/****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include "StdAfx.h"
#include "O_ITable.h"
#include "O_ITableView.h"
#include "otlkcon_generic_msgstore.h"
#include "otlkcon_debug.h"

O_ITable::O_ITable(void)
{
    lastCalledFunctionID = 0;

	tableName       = NULL;
    objName = NULL;
    className = NULL;

//    sqlObj = NULL;
    memObj = NULL;
    indexPropTag = 0;
    cursor = 0;
        derefAssocObj = 0;
    currentRestriction = NULL;

    cRef = 0;

    // FIXME: We need to find the best scope for this.
    srand((unsigned int)GetTickCount());

	InitializeCriticalSection(&objectCS);

    adviseSink = new O_ITableAdviseSink( this );

    initialized = true;
}

O_ITable::~O_ITable(void)
{
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, L"%S->O_ITable::~O_IProp(): Goodbye cruel world!....", className);
	DeleteCriticalSection(&objectCS);
    Destroy();
}

HRESULT O_ITable::Create(char *df, LPSPropTagArray defaultTags, ULONG indexTag,
              MAPIUID ig, otlkcon_generic_msgstore *mstore)
{
    HRESULT result = S_OK;
	char *strBuf, *strBuf2;
    MAPIUID typeGuid;
    int sqlRes;
    int rowCount;

    _ASSERT( !IsBadReadPtr(this, 1) );

    cRef = 1;

    // This should be checked on insertions
    if( indexTag )
        indexPropTag = indexTag;
    else indexPropTag = PR_INSTANCE_KEY;

    msgstore = mstore;
    memObj = new otlkcon_memory( msgstore->memObj );

    objName   = memObj->_strdup(" ");
    className = memObj->_strdup("O_ITable");
    databaseFilename = memObj->_strdup(df);

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Create");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::Create");   

    //An sqlite_open() must be done on each thread
	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    result = sqlObj->sqlCmdBegin();
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }
    
	sqlObj->init("ITable::Create",databaseFilename);

    // If ig is GUID_NULL, then get a new guid
    if( memcmp(&ig,&otlkcon_null_guid,sizeof(MAPIUID)) == 0 )
    {
        result = O_IProp::NewMAPIUID( &ig );
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK;
        }
    }

    strBuf = (char *)memObj->malloc( 40*sizeof(wchar_t)  );
    strBuf2= (char *)memObj->malloc( 40 );
    StringFromIID(  IID_IMAPITable,  (LPWSTR*)&strBuf );
    _snprintf(strBuf2, 39, "%S", strBuf);

    result = O_IProp::String2MAPIUID( &typeGuid, strBuf2);
    result = O_IProp::GetTableName(ig, typeGuid, &tableName, memObj->lpAllocateBuffer);

    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
        goto create_failed;
    }

    //Does the table already exist?
    sqlRes = sqlObj->sqlCmdSingleInt(&rowCount, 0,
        "SELECT COUNT(*) FROM sqlite_master WHERE ( type='table' ) AND (name='%s');",
            tableName );
    if( sqlRes != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    if ( rowCount == 0 )
    {
        //Create the table
        sqlRes = sqlObj->sqlCommand("CREATE TABLE %s ( RowNum INTEGER NOT NULL, \
                                    ColID  INTEGER NOT NULL, \
                                    PropTag INTEGER NOT NULL, \
                                    MV_SEQ INTEGER NOT NULL,  \
                                    SEQ INTEGER NOT NULL,     \
                                    IS_REF INTEGER NOT NULL,  \
                                    DATA BLOB )", tableName);

    }
    else
    {
        //Table exists
        result = MAPI_E_COLLISION;
        goto create_failed;
    }
    sqlRes = sqlObj->sqlCmdCommit();

    if( defaultTags )
    {
        result = Init( defaultTags );
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK;
        }
    }

    // Listen to ourselves for delete requests
    result = InternalAdvise( fnevTableModified,  adviseSink, NOTIFY_SYNC, &objectCreateConnection);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Create");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Create");
    return result;

create_failed:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Create");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Create");
    sqlObj->sqlCmdRollback();
    return result;
}

HRESULT O_ITable::Duplicate( O_ITable **tbl )
{
    HRESULT result = S_OK;
    MAPIUID ig = {0};
    ULONG ulSize = (ULONG)currentColumns.size();
    LPSPropTagArray defaultTags;
    LPSRow currRow = NULL;

    *tbl = new O_ITable();

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Duplicate");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::Duplicate");

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    defaultTags 
        = (LPSPropTagArray)memObj->malloc( CbNewSPropTagArray( ulSize ) );
    defaultTags->cValues = ulSize;

    for(ULONG i=0; i<ulSize; i++)
    {
        defaultTags->aulPropTag[i] = currentColumns[i];
    }

    result = (*tbl)->Create(databaseFilename, defaultTags, indexPropTag, ig, 
        msgstore );

    // FIXME: This should be done at the SQL level, but for now
    //        this should do.
    for( ULONG i=0; i<currentRows.size(); i++)
    {
        result = GetRowAt( currentRows[i], &currRow);
        result = (*tbl)->HrModifyRow( currRow );
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Create");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Create");

    return result;
}

HRESULT O_ITable::Init( LPSPropTagArray defaultTags )
{
    HRESULT result = S_OK;
    list<long> tagList;
    ULONG j=0;
    int sqlRes;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Init");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::Init"); 

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
    sqlRes = sqlObj->sqlCmdBegin();

    // Setup rows -1 and 0

    // TODO: Are dupe properties ok?  PR_NULL needs to be
    //       allowed in duplicate at least.

    // Validate row
    for( ULONG i=0; i<defaultTags->cValues; i++ )
    {
        // Ignore PR_NULL
        if( defaultTags->aulPropTag[i] == PR_NULL )
            continue;

        tagList.push_back( defaultTags->aulPropTag[i] );
        j++;
    }

    tagList.sort();
    tagList.unique();

    if( tagList.size() != j )
    {
        // We had duplicates in the default tag list
        OTLKCON_DEBUGBREAK;

        result = MAPI_E_INVALID_PARAMETER;
        goto init_failed;
    }

    int instanceKeyFound = 0;
    //defaultColumns.clear();
    //currentColumns.clear();
    for(ULONG i=0; i<defaultTags->cValues; i++)
    {
        //Since this is Create(), the default propertyTag row
        //and the current propertyTag row are set to the same.
        sqlRes = sqlObj->sqlCommand("INSERT INTO %s VALUES ( -1, %d, %d, 0, 0, 0, '0' );",
             tableName, i, defaultTags->aulPropTag[i]);
        sqlRes = sqlObj->sqlCommand("INSERT INTO %s VALUES ( 0, %d, %d, 0, 0, 0, '0' );",
             tableName, i, defaultTags->aulPropTag[i]);
        
        if( defaultTags->aulPropTag[i] == PR_INSTANCE_KEY )
            instanceKeyFound = 1;
        
        // Setup the in-memory column lists
        defaultColumns.push_back( defaultTags->aulPropTag[i] );
        currentColumns.push_back( defaultTags->aulPropTag[i] );
    }

    if( instanceKeyFound == 0 )
    {
        // PR_INSTANCE_KEY is a required column in all tables
        result = MAPI_E_INVALID_PARAMETER;
        goto init_failed;
    }

    if( j==0 )
        OTLKCON_DEBUGBREAK;

	//We don't need any PR_NULLs in column set
	LPSPropTagArray lpFilteredRows = NULL;
	result = GetColumnSet(&lpFilteredRows);
	if(SUCCEEDED(result))
	{
		result = CreateTable(NULL, 
			memObj->lpAllocateBuffer, memObj->lpAllocateMore, memObj->lpFreeBuffer, 
			0, TBLTYPE_DYNAMIC, indexPropTag, lpFilteredRows, &lpTblData);

		memObj->lpFreeBuffer(lpFilteredRows);
	} // if

    if( FAILED(result) )
    {
        OTLKCON_DEBUGBREAK;
    }

    LPSRow currRow;

    for( ULONG i=0; i<currentRows.size(); i++ )
    {
        result = GetRowAt( currentRows[i], &currRow );
        if( result != S_OK )
        {
            OTLKCON_DEBUGBREAK;
        }
        result = lpTblData->HrModifyRow( currRow  );
        if( FAILED(result) )
        {
            OTLKCON_DEBUGBREAK;
        }
       // result = otlkcon_row2xml(currRow, 0, 0, memObj->lpAllocateBuffer, 
       //     memObj->lpAllocateMore, memObj->lpFreeBuffer,  &strBuff, 2047 );
       // OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L">>>>>ROW<<<<<<\n%S\n", strBuff);
    }

    initialized = true;

    sqlRes = sqlObj->sqlCmdCommit();
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Init");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Init");
    return result;

init_failed:
    sqlRes = sqlObj->sqlCmdRollback();
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Init");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Init");
    return result;
}

HRESULT O_ITable::Load(char *df, char *tableID, otlkcon_generic_msgstore *mstore)
{
    HRESULT result;
    int rowCount;
    const char **queryRemainder = NULL;
    ULONG sqlRes, currTag;
    sqlite3_stmt * queryStmt;
    char * queryStr;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Load");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::Load");
    
    otlkcon_sqlite_helper * sqlObj = GetSqlObj();
    if( NULL == sqlObj )
    {
        OTLKCON_DEBUGBREAK;
	    result = E_FAIL;
	    goto load_fail;
    }

    //result = sqlObj->init("ITable::Create",databaseFilename);  

    sqlRes = sqlObj->sqlCmdBegin();

    msgstore = mstore;
    cRef = 1;

    // This should be checked on insertions
    indexPropTag = PR_INSTANCE_KEY;

    memObj = new otlkcon_memory( msgstore->memObj );

    objName   = memObj->_strdup( " " );
    className = memObj->_strdup( "O_ITable" );
    tableName = memObj->_strdup( tableID );

    // Store the database file name for later use.
    databaseFilename = memObj->_strdup(df);

        
    // The table better exist...
    sqlRes = sqlObj->sqlCmdSingleInt(&rowCount, 0,
        "SELECT COUNT(*) FROM sqlite_master WHERE ( type='table' ) AND (name='%s');",
            tableName );
    if( sqlRes != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    if( rowCount != 1 )
    {
        // Table not found
        OTLKCON_DEBUGBREAK;
    }

    // Now count the columns
    sqlRes = sqlObj->sqlCmdSingleInt(&rowCount, 0,
        "SELECT COUNT(*) FROM ( SELECT * FROM %s WHERE rowNum = -1 )",
            tableName );
    if( sqlRes != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    // Get the default columns
    sqlRes = sqlObj->buildQuery( &queryStr,
               "SELECT propTag FROM %s WHERE rowNum = -1 ORDER BY colID", 
                    tableName );

    // TODO: This entire loop needs to be redone.  Clean up the busy handling.
    sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                                &queryStmt, queryRemainder);
    sqlRes = sqlite3_step(queryStmt);
    if( sqlRes == SQLITE_DONE )
    {
        // No default column set??
        OTLKCON_DEBUGBREAK;
    }
    else if( sqlRes == SQLITE_ROW )
    {
        // TODO: Fix this loop
        while( sqlRes==SQLITE_ROW )
        {
            // Get the PropTag
            currTag = sqlite3_column_int(queryStmt, 0);
            defaultColumns.push_back( currTag );
            currentColumns.push_back( currTag );

            sqlRes = sqlite3_step(queryStmt);
        }
    }
    else
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::Load sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        result = E_FAIL;
        goto load_fail;
    }

    sqlRes = sqlite3_finalize(queryStmt);
    if ( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::Load sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        result = E_FAIL;
        goto load_fail;
    }

    memObj->free(queryStr);

    int rowNum=0;
    // Populate the 'currentRows' vector
    sqlRes = sqlObj->buildQuery( &queryStr,
               "SELECT DISTINCT rowNum FROM %s", tableName );
    sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                                &queryStmt, queryRemainder);
    sqlRes = sqlite3_step(queryStmt);
    if( sqlRes == SQLITE_DONE )
    {
        // No rows at all??
        OTLKCON_DEBUGBREAK;
    }
    else if( sqlRes == SQLITE_ROW )
    {
        while( sqlRes==SQLITE_ROW )
        {
            // Get the PropTag
            rowNum = sqlite3_column_int(queryStmt, 0);

            if( rowNum != -1 && rowNum != 0 )
            {
                defaultRows.push_back( rowNum );
                currentRows.push_back( rowNum );
            }

            sqlRes = sqlite3_step(queryStmt);
        }
    }
    else
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::Load sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        result = E_FAIL;
        goto load_fail;
    }

    sqlRes = sqlite3_finalize(queryStmt);
    if ( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::Load sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        result = E_FAIL;
        goto load_fail;
    }

    memObj->free(queryStr);

	//populating TableData
	//We don't need any PR_NULLs in column set
	LPSPropTagArray lpFilteredRows = NULL;
	result = GetColumnSet(&lpFilteredRows);
	if(SUCCEEDED(result))
	{
		result = CreateTable(NULL, 
			memObj->lpAllocateBuffer, memObj->lpAllocateMore, memObj->lpFreeBuffer, 
			0, TBLTYPE_DYNAMIC, indexPropTag, lpFilteredRows, &lpTblData);

		memObj->lpFreeBuffer(lpFilteredRows);
	} // if

	if( FAILED(result) )
	{
		OTLKCON_DEBUGBREAK;
	}

	LPSRow currRow;

	for( ULONG i=0; i<currentRows.size(); i++ )
	{
		result = GetRowAt( currentRows[i], &currRow );
		if( result != S_OK )
		{
			OTLKCON_DEBUGBREAK;
		}
		result = lpTblData->HrModifyRow( currRow  );
		if( FAILED(result) )
		{
			OTLKCON_DEBUGBREAK;
		}
		// result = otlkcon_row2xml(currRow, 0, 0, memObj->lpAllocateBuffer, 
		//     memObj->lpAllocateMore, memObj->lpFreeBuffer,  &strBuff, 2047 );
		// OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L">>>>>ROW<<<<<<\n%S\n", strBuff);
	}

    // Listen to ourselves for delete requests
    result = InternalAdvise( fnevTableModified,  adviseSink, NOTIFY_SYNC, &objectCreateConnection);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

	initialized = true;

//load_end:
    sqlRes = sqlObj->sqlCmdCommit();

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Load");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Load");
    
    return result;

load_fail:
    sqlRes = sqlObj->sqlCmdRollback();

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Load");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Load");
    
    return result;
}

STDMETHODIMP O_ITable::Dump( ULONG flag )
{
    HRESULT result = S_OK;

    char *tmpStr = (char *)memObj->pMalloc( OTLKCON_LOG_MSG_MAX_LEN*10 );

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Dump");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::Dump");

    if( flag & OTLKCON_DUMP_XML_INCLUDE_ROOT )
    {
        result = otlkcon_obj2xml_begin(&tmpStr, OTLKCON_LOG_MSG_MAX_LEN-1);
    }

    result = otlkcon_table2xml((LPMAPITABLE)this, NULL, objName, 
                OTLKCON_OBJ2XML_SHOW_BINARY, 0, 
                memObj->lpAllocateBuffer, memObj->lpAllocateMore, 
                memObj->lpFreeBuffer, &tmpStr, OTLKCON_LOG_MSG_MAX_LEN*10-1);

    if( flag & OTLKCON_DUMP_XML_INCLUDE_ROOT )
    {
        result = otlkcon_obj2xml_end(&tmpStr, OTLKCON_LOG_MSG_MAX_LEN*10-1);
    }
  
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::Dump");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::Dump");

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL|OTLKCON_LOG_FLAG_NO_DATE, 
                                                                L"%S", tmpStr);

    memObj->pFree( tmpStr );

    return result;
}

#ifdef OTLKCON_TEST_O_ITABLE
STDMETHODIMP O_ITable::Test(char *df, 
                            LPALLOCATEBUFFER lb, LPALLOCATEMORE lm , LPFREEBUFFER lf)
{
    HRESULT result = S_OK;
    LPSRow row = NULL;
    LPSPropValue props = NULL;
    MAPIUID instanceGuid = {0};
    int tagCount = 7;
    LPSPropTagArray testTags;
    char *strBuff; 

    _ASSERT( !IsBadReadPtr(this,1) );

    otlkcon_memory *mem = new otlkcon_memory(lb, lm, lf);

    testTags = (LPSPropTagArray)mem->malloc( CbNewSPropTagArray(10) );

    tagCount = 0;
    testTags->aulPropTag[tagCount++] = PR_INSTANCE_KEY;
    testTags->aulPropTag[tagCount++] = PR_ENTRYID;
    testTags->aulPropTag[tagCount++] = PR_DISPLAY_NAME;
    testTags->aulPropTag[tagCount++] = PR_NULL;
    testTags->aulPropTag[tagCount++] = PR_OBJECT_TYPE;
    testTags->aulPropTag[tagCount++] = PR_COMMENT;
    testTags->aulPropTag[tagCount++] = PR_NULL;
    testTags->cValues = tagCount;

    result = Create(df,testTags,PR_INSTANCE_KEY,instanceGuid,msgstore);

    row   = (LPSRow)memObj->malloc( sizeof(SRow) );
    props = (LPSPropValue)memObj->malloc( tagCount*sizeof(SPropValue) );

    LPBYTE pBin = (LPBYTE)memObj->malloc( 5 );
    memset(pBin, 15, 5);

    tagCount = 0;
    props[tagCount].ulPropTag = PR_INSTANCE_KEY;
    props[tagCount].Value.bin.cb = 5;
    props[tagCount++].Value.bin.lpb = pBin; //Dummy data

    props[tagCount].ulPropTag = PR_ENTRYID;
    props[tagCount].Value.bin.cb = 5;
    props[tagCount++].Value.bin.lpb = pBin;

    props[tagCount].ulPropTag = PR_DISPLAY_NAME;
    props[tagCount++].Value.lpszA = memObj->_strdup("Test display name");

    props[tagCount].ulPropTag = PR_OBJECT_TYPE;
    props[tagCount++].Value.l = 1;

    props[tagCount].ulPropTag = PR_COMMENT;
    props[tagCount++].Value.lpszA = memObj->_strdup("Some comment");

    row->lpProps = props;
    row->cValues = tagCount;

    //Simple put
    result = SetRowAt(1, row);

    //Simple put
    // Note that SetRowAt() ignores the instance key
    // property by design, that's HrModifyRow()'s job
    result = SetRowAt(2, row);

    strBuff = (char *)memObj->malloc( 2048 );

    //Simple get
    LPSRow retRow = NULL;
    result = GetRowAt(1, &retRow);
    result = otlkcon_row2xml(retRow, 0, 0, lb, lm, lf, &strBuff, 2047 );
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%S\n", strBuff);

    //Modify
    row->lpProps[3].Value.l = 2;
    result = HrModifyRow(row);

    //Simple delete
    result = SetRowAt(1, NULL);
    result = SetRowAt(2, NULL);

    //Add
    result = HrModifyRow(row);

    //Delete
    result = HrDeleteRow( props );

    //Add again
    result = HrModifyRow(row);
    pBin[1] = 0;
    result = HrModifyRow(row);
    pBin[1] = 1;
    result = HrModifyRow(row);
    pBin[1] = 2;
    result = HrModifyRow(row);
    pBin[1] = 3;
    result = HrModifyRow(row);

    //Query
    LPSRowSet rows = NULL;
    result = QueryRows(100, 0, &rows);
    for( ULONG i=0; i<rows->cRows; i++ )
    {
        strBuff[0] = 0;
        result = otlkcon_row2xml(&(rows->aRow[i]), 0, 0, lb, lm, lf, &strBuff, 2047);
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%S\n", strBuff);
    }
	if(NULL != rows)
	{
		FreeProws(rows);
		rows = NULL;
	} // if
    //SetColumns
    testTags->aulPropTag[2] = PR_MDB_PROVIDER;
    testTags->cValues = 3;
    result = SetColumns( testTags, 0 );

    //Query after setcolumns
    result = SeekRow( BOOKMARK_BEGINNING, 0, NULL);
    result = QueryRows(100, 0, &rows);
    for( ULONG i=0; i<rows->cRows; i++ )
    {
        strBuff[0] = 0;
        result = otlkcon_row2xml(&(rows->aRow[i]), 0, 0, lb, lm, lf, &strBuff, 2047);
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L">>>>>After SetColumns()<<<<<\n%S\n", strBuff);
    }
	if(NULL != rows)
	{
		FreeProws(rows);
		rows = NULL;
	} // if

    return result;
}
#endif // OTLKCON_TEST_O_ITABLE

void O_ITable::Destroy()
{
    ;
}

/*
STDMETHODIMP O_ITable::QueryMoreRows(LONG lRowCount,
        ULONG ulFlags, LPSRowSet *lppRows )
{
    HRESULT result = S_OK;
    LPSRowSet currRS = *lppRows;
    O_ENTRYID *currEntryID = NULL;
    ULONG objType = 0, i = 0;
    O_IMessage *msg = NULL;
    LPSPropValue propResult = NULL;
    ULONG propCount = 0,l = 0,propValueResultCount = 0, found = 0;
    SPropValue propValueResult[100] = {0}; //FIXME: arbituary limit
    LPSRowSet retSet  = NULL;

	//Henry 30.11.2005:
	//We need to retrieve only props that is in the current column set
	//So better to move that code to the SetColumns function
	//if columns are missing in the current set 
	//we need to retrieve that properties from object 
	//and store them in the correspondent table
	//So we will have all needed properties in the table 
	//and we will need to do that operation only once per column
	//doing so we can have empty initial column set for a table

    // TODO: If this is a contents table, then we have
    // to do a GetProps() on the respective object
    // right here.  For each row, use the instances key
    // to get all the object properties and merge with
    // the rows in the resultant set from above.

    // FIXME: The remainder of this function is very
    // ugly.  Please fix.  One possible solution could
    // be that O_ITable->QueryRows() would look for a
    // special Otlkcon property that stores the associated
    // object's SQL table name.  A second query right in
    // QueryRows would pull in the rest of the properties.

    for( i=0; i<currRS->cRows; i++)
    {
        // for each row
        for( ULONG j=0; j<currRS->aRow[i].cValues; j++ )
        {
            // Does the result contain an entry id?
            if( currRS->aRow[i].lpProps[j].ulPropTag == PR_ENTRYID )
            {
                // Open that object and append the data found
                currEntryID = (O_ENTRYID *)(currRS->aRow[i].lpProps[j].Value.bin.lpb);
                result = msgstore->OpenEntry(sizeof(O_ENTRYID), (LPENTRYID)currEntryID, &IID_IMAPIProp, 
        0, &objType, (LPUNKNOWN*)&msg);
                if( result != S_OK )
                {
                    // This isn't good...
                    OTLKCON_DEBUGBREAK;
                    return result;
                }
				
				result = msg->GetProps(NULL, 0, &propCount, &propResult);
                if( result != S_OK )
                {
                    OTLKCON_DEBUGBREAK;
                    return result;
                }

                // Merge the 2 property value arrays into a new array.
                memset(&propValueResult,0,sizeof(propValueResult));
                memcpy(&propValueResult,currRS->aRow[i].lpProps,
                    sizeof(SPropValue)*currRS->aRow[i].cValues);
                propValueResultCount = currRS->aRow[i].cValues;
                
                // TODO: Copy pointer memory areas, free original results

                // Copy all the retrieved properties to the new array

                for( ULONG k =0; k<propCount; k++ )
                {
                    found = 0;
                    for(l=0; l<currRS->aRow[i].cValues; l++ )
                    {
                        if( propValueResult[l].ulPropTag == propResult[k].ulPropTag )
                        {
                            found = 1;
                            break;
                        }
                    }

                    if( found == 0 )
                    {
                        propValueResultCount++;
                        l += k;
                    }

                    propValueResult[l] = propResult[k];
                }

                break;
            }
        }
    }

    // Put propValueResult in a SRowSet and return that
    // if we have something to return
    if( propValueResultCount > 0 )
    {
        retSet = (LPSRowSet)memObj->malloc( CbNewSRowSet(propValueResultCount ) );
        retSet->cRows = propValueResultCount;
        memcpy( retSet->aRow, propValueResult, propValueResultCount * sizeof(SRow) );
        *lppRows = retSet;
    }

    return result;
}

*/

// Returns a row from the table by rowNum
STDMETHODIMP O_ITable::GetRowAt(ULONG rowNum, LPSRow *lppRow, TagSet *lpTagSet)
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt;
    char * queryStr;
    const char **queryRemainder = NULL;
    int sqlRes;
    int rowCount = 0, colCount = 0;
    unsigned char *data;
    ULONG dataSize = 0, tmpBinSize = 0;
    LPBYTE tmpBin, tmpSQLBin;
    vector<long> currCols;
    FILETIME tempFT = {0};

    // Lock the table object
    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetRowAt");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::GetRowAt");

    //Refresh the database handle if needed
	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    //Does this row exist?
    sqlRes = sqlObj->sqlCmdSingleInt( &rowCount, 0,
        "SELECT COUNT(*) FROM %s WHERE rowNum = %d;", tableName, rowNum);
    if( sqlRes != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    if( rowCount == 0 )
    {
        //No, it doesn't
        result = MAPI_E_NOT_FOUND;
        goto getrowat_end;
    }

    if(NULL != lpTagSet)
    {
		TagSet::iterator iTag = lpTagSet->begin();
		while(iTag != lpTagSet->end()) 
		{
			currCols.push_back( (long)*iTag );
			++iTag;
		} // while
    } // if
	else
	{
		// Remove any PR_NULLS in the current column set.
		for( ULONG i=0; i<currentColumns.size(); i++ )
		{
			if( currentColumns[i] == PR_NULL )
				continue;
			currCols.push_back( currentColumns[i] );
		}
	} // else

    //Build a SRow to hold the result
    int propCount = (int)currCols.size();
    *lppRow = (LPSRow)memObj->malloc( sizeof(SRow) );
    LPSPropValue props 
        = (LPSPropValue)memObj->mallocMore(*lppRow, propCount*sizeof(SPropValue) );
    (*lppRow)->cValues = propCount;

    ULONG currTag;
    for( int i=0; i<propCount; i++ )
    {
        currTag = currCols[i];
        props[i].ulPropTag = currTag;

        // FIXME: This needs to be prepared.
        sqlRes = sqlObj->buildQuery( &queryStr,
               "SELECT * FROM %s WHERE rowNum = %d AND PropTag = %d;", 
                    tableName, rowNum, currTag );


        sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                                    &queryStmt, queryRemainder);
        sqlRes = sqlite3_step(queryStmt);
        if( sqlRes == SQLITE_DONE )
        {
            // No rows found.  Maybe the column was added
            // after the table was created using SetColumns()
            props[i].ulPropTag = PROP_TAG(PT_ERROR, PROP_ID(currTag));
            props[i].Value.l   = MAPI_E_NOT_FOUND;
        }
        else if ( sqlRes == SQLITE_ROW )
        {
            switch( PROP_TYPE(props[i].ulPropTag) )
            {
                case PT_I2:
                    // short i 
                    props[i].Value.i 
                        = sqlite3_column_int(queryStmt, OTLKCON_ITABLE_TABLE_DATA_COL);
                    break;

                case PT_LONG:
                    // long l or ul??
                    props[i].Value.l 
                        = sqlite3_column_int(queryStmt, OTLKCON_ITABLE_TABLE_DATA_COL);
                    break;
                case PT_BOOLEAN:
                    // long l or ul??
                    props[i].Value.b 
                        = sqlite3_column_int(queryStmt, OTLKCON_ITABLE_TABLE_DATA_COL);
                    break;

                case PT_STRING8:
                    // LPSTR lpszA 
                    // FIXME: How do you free this memory??
                    data = (unsigned char *)sqlite3_column_text(queryStmt, 
                                                        OTLKCON_ITABLE_TABLE_DATA_COL);
                    props[i].Value.lpszA = (char *)memObj->_strdupMore(*lppRow, (const char *)data );
                    break;

                case PT_BINARY:
                    // SBinary bin
                    tmpBinSize = sqlite3_column_bytes(queryStmt, 
                                                        OTLKCON_ITABLE_TABLE_DATA_COL);
                    tmpBin = (LPBYTE)memObj->mallocMore( *lppRow, tmpBinSize+1 );
                    tmpSQLBin = (LPBYTE)sqlite3_column_blob(queryStmt, 
                                            OTLKCON_ITABLE_TABLE_DATA_COL);
                    memcpy(tmpBin, tmpSQLBin, tmpBinSize);
                    props[i].Value.bin.lpb = tmpBin;
                    props[i].Value.bin.cb  = tmpBinSize;
                    break;

                case PT_SYSTIME:
                    // FILETIME ft
                    tmpBinSize = sqlite3_column_bytes(queryStmt, 
                                                        OTLKCON_ITABLE_TABLE_DATA_COL);
                    if( tmpBinSize != sizeof(FILETIME) )
                    {
                        OTLKCON_DEBUGBREAK;
                    }

                    tmpSQLBin = (LPBYTE)sqlite3_column_blob(queryStmt, 
                                            OTLKCON_ITABLE_TABLE_DATA_COL);
                    memcpy(&tempFT, tmpSQLBin, sizeof(FILETIME));
                    props[i].Value.ft = tempFT;
                    break;

                default:
                    // FIXME:  What about tables embedded in Tables???
                    //         We should be checking the IS_REF Column
                    //         when we encounter PT_OBJECT.... or something

                    // Unknown type
                    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
                        L"ITABLE::GetRowAt unknown type: %X", 
                        PROP_TYPE(props[i].ulPropTag) );
                    OTLKCON_DEBUGBREAK;
            }
        }
        sqlRes = sqlite3_finalize(queryStmt);
        memObj->free(queryStr);

        if ( sqlRes != SQLITE_OK )
        {
            OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
                L"ITable::GetRowAt sqlite3_finalize error: %S", 
                sqlite3_errmsg(sqlObj->databaseHandle));
            result = E_FAIL;
            goto getrowat_end;
        } 
    }
    (*lppRow)->lpProps = props;

getrowat_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::GetRowAt");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetRowAt");
    return result;
}

// Sets the specified row.
// If lpSRow is NULL, then the row, rowNum, is deleted
STDMETHODIMP O_ITable::SetRowAt(ULONG rowNum, LPSRow lpSRow)
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt;
    char * queryStr;
    const char **queryRemainder = NULL;
    ULONG sqlRes, currTag, j;
    int column;
    list<long> tagList;
    list<long> currCols;
    list<long> setRes;

	// Lock the table object
    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::SetRowAt");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::SetRowAt");

    //Refresh the database handle if needed
	otlkcon_sqlite_helper * sqlObj = GetSqlObj();
 
    //Begin the transaction
    sqlRes = sqlObj->sqlCmdBegin();

    if( lpSRow == NULL )
    {
        // Delete the row
        sqlRes = sqlObj->sqlCommand("DELETE FROM %s WHERE rowNUM = %d",
                                            tableName, rowNum );
        goto setrowat_end;
    }

	TRACE_ROW(OTLKCON_LOG_TARGET_ALL, lpSRow, L"O_ITable::SetRowAt");
    
	// Validate row
    for( j=0; j<lpSRow->cValues; j++ )
    {
        if( lpSRow->lpProps[j].ulPropTag == PR_NULL )
        {
            // PR_NULL properties are invalid as columns.
            // PR_NULL columns in the table are placeholder
            // columns only.
            result = MAPI_E_INVALID_PARAMETER;
            goto setrowat_failed;
        }

        tagList.push_back( lpSRow->lpProps[j].ulPropTag );
    }

    tagList.sort();
    tagList.unique();

    if( tagList.size() != j )
    {
        // We had duplicate properties in this tag list
        OTLKCON_DEBUGBREAK;

        result = MAPI_E_INVALID_PARAMETER;
        goto setrowat_failed;
    }

    // Remove any PR_NULLS in the default column set.
    for( ULONG i=0; i<defaultColumns.size(); i++ )
    {
        if( defaultColumns[i] == PR_NULL )
            continue;
        currCols.push_back( defaultColumns[i] );
    }

    //Henry 1.12.2005
	//We need to insert all default properties to the table

	// FIXME: When setting a row, do we need to set all
    //        columns everytime?  Also, are properties
    //        in the default column set but not in the
    //        current column set allowed?
    //currCols.sort();
    //if( tagList != currCols )
    //{
    //    // Current column taglist and insert row tag list
    //    // do not match
    //    result = MAPI_E_INVALID_PARAMETER;
    //    goto setrowat_failed;
    //}

    ULONG propCount = (ULONG)currCols.size();

    // FIXME: This won't work for multi-valued properties or properties
    //        that span multiple rows.
    // TODO: Check the index PropTag.

    for( ULONG i=0; i<lpSRow->cValues; i++)
    {
        currTag = lpSRow->lpProps[i].ulPropTag;

        // We need the right column number for this property
        sqlRes = sqlObj->sqlCmdSingleInt( &column, 0,
        "SELECT colID FROM %s WHERE rowNum = -1 AND propTag = %d", 
                tableName, currTag);
        if( sqlRes != S_OK )
        {
            OTLKCON_DEBUGBREAK;
        }

        // Delete that cell to make Add vs. Modify simplier
        sqlRes = sqlObj->sqlCommand(
            "DELETE FROM %s WHERE rowNum = %d AND propTag = %d",
                tableName, rowNum, currTag );

        //Build query...
        switch( PROP_TYPE(lpSRow->lpProps[i].ulPropTag) )
        {
            case PT_I2:
                // short i 
                sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, '%d' );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0, lpSRow->lpProps[i].Value.i );
                break;

            case PT_LONG:
                // long l or ul??
                sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, '%ld' );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0,
                    lpSRow->lpProps[i].Value.l );
			    sqlRes = sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                                        &queryStmt, queryRemainder);
                break;
            case PT_BOOLEAN:
                // unsigned short int b
                sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, '%d' );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0,
                    lpSRow->lpProps[i].Value.b );
                break;
            case PT_STRING8:
                // LPSTR lpszA 
                // TODO: Check that the size < 1MB 
                sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, '%s' );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0,
                    lpSRow->lpProps[i].Value.lpszA );
                break;

            case PT_BINARY:
                // SBinary bin 
			    sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, :DAT );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0 ); 
                break;

            case PT_SYSTIME:
                // FILETIME ft 
			    sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, :DAT );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0 ); 
                break;

            case PT_UNICODE:
                // LPWSTR lpszW 
                sqlRes = sqlObj->buildQuery( &queryStr,
                    "INSERT OR REPLACE INTO %s VALUES ( %d, %d, %d, %d, %d, %d, '%S' );",
                    tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 0,
                    lpSRow->lpProps[i].Value.lpszW );
                break;

            case PT_NULL:
            case PT_OBJECT:
                // This is PT_OBJECT only for internal use
                // NB: the value is not saved!!!
                // TODO:  Actually, what should happen is that the DATA column
                //        should hold the name of the table that represents this
                //        object.
                if( lpSRow->lpProps[i].ulPropTag == PR_NULL )
                    break;
                sqlRes = sqlObj->buildQuery( &queryStr,
                "INSERT OR REPLACE INTO %s ( rowNum, colID, PropTag, MV_SEQ, SEQ, IS_REF ) VALUES ( %d, %d, %d, %d );",
                        tableName, rowNum, column, lpSRow->lpProps[i].ulPropTag, 0, 0, 1 );

                break;

            default:
                OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
                    L"0x%0.8X : unknown property type.\n", 
                    lpSRow->lpProps[i].ulPropTag);
                OTLKCON_DEBUGBREAK;
                break; 
        }

        sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                        &queryStmt, queryRemainder);

        // Bind if necessary
        switch( PROP_TYPE(currTag) )
        {
            case PT_BINARY:
                sqlRes = sqlite3_bind_blob(queryStmt, 1, lpSRow->lpProps[i].Value.bin.lpb,
                    lpSRow->lpProps[i].Value.bin.cb, SQLITE_TRANSIENT);
                break;

            case PT_SYSTIME:
                sqlRes = sqlite3_bind_blob(queryStmt, 1, &(lpSRow->lpProps[i].Value.ft),
                    sizeof(FILETIME), SQLITE_TRANSIENT);
                break;
        }
        
        //if( !isMV && queryStmt )
        sqlite3_step(queryStmt);
        sqlRes = sqlite3_finalize(queryStmt);
        memObj->free(queryStr);

        if ( sqlRes != SQLITE_OK )
        {
            OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
                L"ITable::SetRowAt sqlite3_finalize error: %S", 
                sqlite3_errmsg(sqlObj->databaseHandle));
            result = E_FAIL;
            goto setrowat_failed;
        }
    }

setrowat_end:
    sqlRes = sqlObj->sqlCmdCommit();
    
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::SetRowAt");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::SetRowAt");
    return result;

setrowat_failed:
    sqlRes = sqlObj->sqlCmdRollback();

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::SetRowAt");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::SetRowAt");
    return result;
}

// Compact rowNum sequence.  This should probably be done
// on Load(), and after a certain number of deletions
STDMETHODIMP CompactRows()
{
    HRESULT result = S_OK;

    return result;
}

//Given an SRow containing a index column, this method
//returns the RowNum of a matching column
STDMETHODIMP O_ITable::_IntFindRow(LPSRow lpSRow, int *rowNum )
{
    HRESULT result = S_OK;
    int found = 0;
    LPSPropValue index;

    if( lpSRow == NULL )
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    //First, find the index column and it's value
    for(ULONG i=0; i<lpSRow->cValues; i++)
    {
        if( lpSRow->lpProps[i].ulPropTag == indexPropTag )
        {
            index = lpSRow->lpProps+i;
            found = 1;
            break;
        }
    }

    if( !found )
    {
        // Not good.  Didn't find the index column
        OTLKCON_DEBUGBREAK;
        return MAPI_E_NOT_FOUND;
    }

    result = _IntFindRow(index, rowNum);

    return result;
}

STDMETHODIMP O_ITable::_IntFindRow(LPSPropValue index, int *rowNum )
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt;
    char * queryStr, *propMatchStr;
    const char **queryRemainder = NULL;
    int sqlRes;

    if( index == NULL )
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::_IntFindRow");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::_IntFindRow");

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    if( PROP_TYPE(index->ulPropTag) == PT_UNSPECIFIED )
    {
        //OTLKCON_DEBUGBREAK;
        // Strip the property type by ANDing with 11111111111111110000000000000000
        propMatchStr = OTLKCON_PT_UNSPECIFIED_PROPTAG_STR;
    }
    else
    {
        propMatchStr = OTLKCON_REGULAR_PROPTAG_STR;
    }

    //Is this row already in the database??
    sqlRes = sqlObj->buildQuery( &queryStr,
            "SELECT MIN(rowNum) \
             FROM \
                ( SELECT DISTINCT rowNum \
                  FROM %s \
                  WHERE data = :DAT \
                    AND %s = %d )",
             tableName, propMatchStr, index->ulPropTag );
    if( sqlRes != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                                    &queryStmt, queryRemainder);
	sqlite3_bind_blob(queryStmt, 1, index->Value.bin.lpb,
                                    index->Value.bin.cb, SQLITE_TRANSIENT);
    sqlRes = sqlite3_step(queryStmt);
    *rowNum = sqlite3_column_int(queryStmt, 0);
    sqlRes = sqlite3_finalize(queryStmt); 
    memObj->free(queryStr);

    if ( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::SetProps sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        SetLastError(E_FAIL, "Error accessing database");
        result = E_FAIL;
        goto intfindrow_end;
    }

    if( *rowNum == 0 )
    {
        // NB: Row number 0 is special
        result = MAPI_E_NOT_FOUND;
    }

intfindrow_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::_IntFindRow");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::_IntFindRow");
    
    return result;
}

STDMETHODIMP O_ITable::HrDeleteRows(
	ULONG ulFlags,
	LPSRowSet lpRowsetToDelete,
	ULONG FAR * cRowsDeleted)
{
	if(NULL == cRowsDeleted) 
	{
		return E_INVALIDARG;
	} // if

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrDeleteRows");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::HrDeleteRows");

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

	HRESULT hResult = S_OK;
	if(SUCCEEDED(hResult))
	{
		if(ulFlags & TAD_ALL_ROWS && NULL == lpRowsetToDelete)
		{
			int cRows = 0;
			hResult = sqlObj->sqlCmdSingleInt( &cRows, 0,
				"SELECT count(rowNum) FROM ( SELECT DISTINCT rowNum FROM %s where rowNum > 0)", tableName);
			if( hResult != S_OK )
			{
				OTLKCON_DEBUGBREAK;
			}
			else if(0 < cRows)
			{
				//we are deleting all rows at once
				hResult = sqlObj->sqlCommand("DELETE FROM %s WHERE rowNum > 0;",
					tableName);
				if( hResult != S_OK )
				{
					OTLKCON_DEBUGBREAK;
				} // if
				else
				{
					ULONG ulRows = 0;
					//clear snapshot
					lpTblData->HrDeleteRows(TAD_ALL_ROWS, NULL, &ulRows);
					*cRowsDeleted = cRows;
				} // else
			} // else if
		} // if
		else
		{
			if(NULL == lpRowsetToDelete || ulFlags & TAD_ALL_ROWS)
			{
				return E_INVALIDARG;
			} // if
			//TODO implementation
		} // else
	} // if

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::HrDeleteRows");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrDeleteRows");   

	return hResult;
}


STDMETHODIMP O_ITable::HrDeleteRow(LPSPropValue lpSPropValue)
{
    HRESULT result = S_OK;
    int rowNum;

    if( lpSPropValue == NULL )
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrDeleteRow");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::HrDeleteRow");

    result = _IntFindRow( lpSPropValue, &rowNum );
    switch(result)
    {
        case MAPI_E_NOT_FOUND:
            result = MAPI_E_NOT_FOUND;
            goto hrdeleterow_end;
            break;

        case S_OK:
            // Expected result
            break;

        default:
            // error condition
            OTLKCON_DEBUGBREAK;
    }

    // Delete the row
    result = SetRowAt(rowNum, NULL);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }

    // Update the in-memory views
    for(vector<int>::iterator i = defaultRows.begin();
                                        i != defaultRows.end(); i++)
    {
        if( *i == rowNum )
        {
            defaultRows.erase( i );
            break;
        }
    }

    // Notify all interested listeners

    // FIXME: We should check first that the deleted row
    //        was in the current view.
    LPNOTIFICATION notif = (LPNOTIFICATION)memObj->malloc( sizeof(NOTIFICATION) );
    notif->ulEventType = fnevTableModified;
    notif->info.tab.ulTableEvent = TABLE_ROW_DELETED;
    memcpy(&(notif->info.tab.propIndex), lpSPropValue, sizeof(SPropValue));
	ULONG ulNotifyFlags = 0;

    // This notify should cause all copies of this table to delete
    // this row in OnNotify()
    result = Notify( NULL, 1, notif, &ulNotifyFlags );

    for(vector<int>::iterator i = currentRows.begin();
                                        i != currentRows.end(); i++)
    {
        if( *i == rowNum )
        {
            currentRows.erase( i );
            break;
        }
    }

    // Update bookmarks
    for(vector<BOOKMARK>::iterator i = bookmarks.begin();
                                        i != bookmarks.end(); i++)
    {
        if( *i == rowNum )
        {
            bookmarks.erase( i );
            break;
        }
    }

hrdeleterow_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::HrDeleteRow");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrDeleteRow");
    
    return result;
}

STDMETHODIMP O_ITable::HrModifyRow(LPSRow lpSRow)
{
    HRESULT result = S_OK;
    int rowNum, sqlRes, action=OTLKCON_ACTION_NONE;

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    if( lpSRow == NULL )
    {
        return MAPI_E_INVALID_PARAMETER;
    }

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrModifyRow");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::HrModifyRow");

    //First, find the index column and it's value
    result = _IntFindRow( lpSRow, &rowNum );
    switch(result)
    {
        case MAPI_E_NOT_FOUND:
            // Add row, hence find the rowNum
            sqlRes = sqlObj->sqlCmdSingleInt( &rowNum, 0,
                "SELECT MAX(rowNum)+1 FROM ( SELECT DISTINCT rowNum FROM %s )", tableName);
            if( sqlRes != S_OK )
            {
                OTLKCON_DEBUGBREAK;
            }
            action=OTLKCON_ACTION_ADD;
            break;
        case S_OK:
            // Modify row
            action=OTLKCON_ACTION_MOD;
            break;
        default:
            // Error condition
            break;
    }

    result = SetRowAt(rowNum, lpSRow);
    if( result != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }
    
    if( action == OTLKCON_ACTION_ADD )
    {
        // Add row to default view
        defaultRows.push_back( rowNum );
    }

    // Check the row to see if it is still
    // visible per the current restriction.
    
    int visible=0;

    if( currentRestriction )
        result = doRestriction(lpSRow,currentRestriction,&visible);

    if( visible || currentRestriction==NULL )
    {
        int found=0;

        //Add to current view, if it's not there already
        for(ULONG i=0; i<currentRows.size(); i++ )
        {
            if( currentRows[i] == rowNum )
            {
                found=1;
                break;
            }
        }

        if( found==0 )
            currentRows.push_back(rowNum);

        //Sort the table
    }

    // Update the shadow object
    result = lpTblData->HrModifyRow( lpSRow );
    if( FAILED(result) )
    {
        OTLKCON_DEBUGBREAK;
    }
 
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::HrModifyRow");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrModifyRow");
   
    return result;
}

STDMETHODIMP O_ITable::HrGetView( LPSSortOrderSet lpSSortOrderSet,
    CALLERRELEASE *lpfCallerRelease, ULONG ulCallerData, LPMAPITABLE *lppMAPITable )
{
    HRESULT result = S_OK;

    // FIXME:  For now, this is a great way to minimize the
    //         use of O_ITable when it is not absolutely
    //         needed, and is also still buggy.  This
    //         implementation returns a view from a
    //         ITableData Object initialized off this object.
    //         The view is type O_ITableView, which is a thin
    //         wrapper around IMAPITable with the Advise() and
    //         Unadvise() wrapped to notify the parent object.
    //
    //         But, eventually the returned view should be a
    //         O_ITable object as well.... I guess...

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrGetView");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::HrGetView");

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    //FIXME: This fails when the table is empty.
    result = lpTblData->HrGetView( NULL, NULL, NULL, lppMAPITable );
    if( FAILED(result) )
    {
        OTLKCON_DEBUGBREAK;
    }

    // Create the O_ITableView object
    O_ITableView *tbl = new O_ITableView();
    result = tbl->Create(*lppMAPITable, this);

    *lppMAPITable = (LPMAPITABLE)tbl;

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::HrGetView");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::HrGetView");

    return result;
}


STDMETHODIMP O_ITable::QueryInterface (REFIID riid, void **ppv)
{
    HRESULT result = S_OK;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"inside O_ITable->QueryInterface. \
 {%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}\n",
        riid.Data1, riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1],
        riid.Data4[2], riid.Data4[3], riid.Data4[4], riid.Data4[5],
        riid.Data4[6], riid.Data4[7]);

    *ppv = NULL;

    if( memcmp(&riid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(&riid, &IID_IMAPITable, sizeof(IID)))
    { 
        result = ResultFromScode(E_NOINTERFACE);
        return result;
    }

    AddRef();
    *ppv = (LPVOID)this;

    return result;
}

ULONG STDMETHODCALLTYPE O_ITable::AddRef (void)
{
    return ++cRef;
}

ULONG STDMETHODCALLTYPE O_ITable::Release (void)
{
    if ( --cRef == 0 )
    {
        //int sqlRes;

        //TODO: A much more thorough job needs to be done
        //sqlRes = sqlObj->sqlCommand("DROP TABLE %s", tableName);
    }

    return cRef;
}

STDMETHODIMP_(otlkcon_sqlite_helper *) O_ITable::GetSqlObj()
{
    HRESULT result = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetSqlObj");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::GetSqlObj");

	//Henry 30.12.2005: See comments in O_IProp::GetSqlObj
	otlkcon_sqlite_helper * sqlObj = 
		(otlkcon_sqlite_helper *)TlsGetValue(otlkcon_tlsIndex);
	if( sqlObj == NULL )
	{
		sqlObj = new otlkcon_sqlite_helper( databaseFilename, memObj );
		if(NULL != sqlObj)
		{
			if(!TlsSetValue( otlkcon_tlsIndex, sqlObj )) 
			{
				OTLKCON_DEBUGBREAK;
				delete sqlObj;
				sqlObj = NULL;
				SetLastError(E_FAIL, "Failed to get database connection");
			} // if
		} // if
		else
		{
			OTLKCON_DEBUGBREAK
				SetLastError(E_FAIL, "Failed to get database connection");
		} // else
	}

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::GetSqlObj");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetSqlObj");

    return sqlObj;
}

HRESULT O_ITable::SetLastError(HRESULT errID, char *errStr)
{
    HRESULT result = S_OK;

    lastErrorMsgID = errID;

    if( errID == S_OK )
    {
        strncpy(lastErrorMsg, OTLKCON_SUCCESS_ERROR_STR, OTLKCON_GETLASTERROR_MSG_SIZE);
    }
    else if( strlen(errStr) )
    {
        strncpy( lastErrorMsg, errStr, OTLKCON_GETLASTERROR_MSG_SIZE );
    }
    else
    {
        strncpy( lastErrorMsg, "Unspecified error", OTLKCON_GETLASTERROR_MSG_SIZE );
    }

    lastErrorMsgID = errID;

    return result;
}

STDMETHODIMP O_ITable::GetLastError(HRESULT hResult,
  ULONG ulFlags,
  LPMAPIERROR *lppMAPIError  
)
{
    HRESULT result = S_OK;
    char *errMsg = NULL;

    if( ulFlags & MAPI_UNICODE )
    {
        return MAPI_E_BAD_CHARWIDTH;
    }

    *lppMAPIError = (LPMAPIERROR)memObj->malloc(sizeof(MAPIERROR));

    errMsg 
        = (char *)memObj->mallocMore(*lppMAPIError, OTLKCON_GETLASTERROR_MSG_SIZE);

    if( objName && strlen(objName) )
    {
        sprintf(errMsg, "%s:%s :: ", className, objName);
    }
    else
    {
        strcpy(errMsg, "<Unnamed object>:: ");
    }

    if( lastCalledFunctionID == 0 )
    {
        strcat(errMsg, "<Undefined function>");
    }
    else
    {
        strcat(errMsg, lastCalledFunctionName);
    }

    _snprintf(errMsg+strlen(errMsg),OTLKCON_GETLASTERROR_MSG_SIZE-strlen(errMsg)-1,
                    " :0x%0.8X:%s", lastErrorMsgID, lastErrorMsg);

    (*lppMAPIError)->ulVersion = MAPI_ERROR_VERSION;
    (*lppMAPIError)->lpszError = errMsg;
    errMsg = (char *)memObj->_strdupMore(*lppMAPIError, 
                                        "OpenConnector Message Store");
    (*lppMAPIError)->lpszComponent = errMsg;

    return result;
}

STDMETHODIMP O_ITable::InternalAdvise (
	ULONG				ulEventMask,
	LPMAPIADVISESINK	lpAdviseSink,
	ULONG				ulFlags,
	ULONG *				lpulConnection)
{
	HRESULT result = S_OK;
	LPNOTIFKEY lpKey = NULL;

	OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
		L"inside ITable->InternalAdvise.\n");

	lpKey = (LPNOTIFKEY)memObj->malloc( sizeof(NOTIFKEY) + (ULONG)strlen(tableName) );
	lpKey->cb = (ULONG)strlen( tableName );
	memcpy(&lpKey->ab[0], tableName, strlen( tableName ) );

	result = msgstore->lpMAPISup->Subscribe( lpKey, ulEventMask, 
		ulFlags, lpAdviseSink, lpulConnection);

	memObj->free(lpKey);
	return result;
}

STDMETHODIMP O_ITable::Advise( ULONG ulEventMask,
  LPMAPIADVISESINK lpAdviseSink,
  ULONG *lpulConnection       
)
{
    HRESULT result = S_OK;
//    LPNOTIFKEY lpKey;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"inside ITable->Advise.\n");

//    lpKey = (LPNOTIFKEY)memObj->malloc( sizeof(NOTIFKEY) + (ULONG)strlen(tableName) );
//    lpKey->cb = (ULONG)strlen( tableName );
//    memcpy(&(lpKey->ab), tableName, strlen( tableName ) );
//
//    result = msgstore->lpMAPISup->Subscribe( lpKey, ulEventMask, 
//                                0, lpAdviseSink, lpulConnection);

	//For client Advise we using deferred notifications
	result = InternalAdvise(ulEventMask, lpAdviseSink, 0, lpulConnection);
    
	return result;
}

HRESULT O_ITable::Notify( O_ENTRYID *key, ULONG cNotification,
  LPNOTIFICATION lpNotifications,
  ULONG *lpulFlags )
{
    HRESULT result = S_OK;
    LPNOTIFKEY lpKey;

    if( key == NULL )
    {
        lpKey = (LPNOTIFKEY)memObj->malloc( sizeof(NOTIFKEY) 
                                            + (ULONG)strlen(tableName) );
        lpKey->cb = (ULONG)strlen( tableName );
        memcpy(&(lpKey->ab), tableName, strlen( tableName ) );
    }
    else
    {
        lpKey = (LPNOTIFKEY)memObj->malloc( sizeof(NOTIFKEY) + sizeof( O_ENTRYID) );
        lpKey->cb = sizeof( O_ENTRYID );
        memcpy(&(lpKey->ab), key, sizeof( O_ENTRYID ) );
    }

    result = msgstore->lpMAPISup->Notify( lpKey, cNotification,
                                    lpNotifications, lpulFlags );

    return result;
}

STDMETHODIMP O_ITable::Unadvise( ULONG ulConnection )
{
    HRESULT result = S_OK;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
        L"inside ITable>Unadvise.\n");

    result = msgstore->lpMAPISup->Unsubscribe( ulConnection );

    return result;
}


STDMETHODIMP O_ITable::GetStatus( 
  ULONG *lpulTableStatus,
  ULONG *lpulTableType  
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    OTLKCON_DEBUGBREAK;

    return result;
}

STDMETHODIMP O_ITable::SetColumns( LPSPropTagArray lpPropTagArray, ULONG ulFlags )
{
    HRESULT result = S_OK;
    int sqlRes, pos = 0, found = 0;
    vector<long> newOrder;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::SetColumns");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::SetColumns");

    lastCalledFunctionID = OTLKCON_ITABLE_SETCOLUMNS;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_SETCOLUMNS_STR);
    result = SetLastError(S_OK, "");

    if( lpPropTagArray == NULL )
    {
        result = SetLastError(MAPI_E_INVALID_PARAMETER, "Tag array can't be NULL");
        result = MAPI_E_INVALID_PARAMETER;
        goto setcolumns_end;
    }

    //otlkcon_prop2str *p2s = new otlkcon_prop2str(memObj->lpAllocateBuffer,
    //                                                        memObj->lpFreeBuffer);
    //for( ULONG j=0; j<lpPropTagArray->cValues; j++ )
    //{
    //    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%d - %S\n", 
    //        j,p2s->get( lpPropTagArray->aulPropTag[j] ) );
    //}
    //OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"\n=======\n\n");
    //for( ULONG j=0; j<currentColumns.size(); j++ )
    //{
    //    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%d - %S\n", 
    //        j,p2s->get( currentColumns[j] ) );
    //}
    //OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"\n=======\n\n");
    //for( ULONG j=0; j<defaultColumns.size(); j++ )
    //{
    //    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%d - %S\n", 
    //        j, p2s->get( defaultColumns[j] ) );
    //}

	otlkcon_sqlite_helper * sqlObj = GetSqlObj();

    // This new order must be a proper subset of the
    // original order.  PR_NULL is a special case.
    for( ULONG j=0,i=0; j<lpPropTagArray->cValues; j++ )
    {
        found = 0;
        // First search the current column set.
        for(i=0; i<currentColumns.size(); i++ )
        {
            if( currentColumns[i] == lpPropTagArray->aulPropTag[j] )
            {
                found = 1;
                break;
            }
        }

        // If not found, search the default column set
        if( found == 0 )
        {
            // The property was not found in the current
            // column set.
            for(i=0; i<defaultColumns.size(); i++ )
            {
                if( defaultColumns[i] == lpPropTagArray->aulPropTag[j] )
                {
                    found = 1;
                    break;
                }
            }

            if( found )
            {
                // The property was in the default set.
                // Add it to the current set
                newOrder.push_back( lpPropTagArray->aulPropTag[j] );
            }
            else
            {
        //OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"Adding %S\n",
        //    p2s->get( lpPropTagArray->aulPropTag[j] ) );
                // This proptag is not in the defaultColumns.  I guess here
                // we use a PR_NULL if we have one ??
                for(i=0; i<defaultColumns.size(); i++ )
                {
                    if( defaultColumns[i] == PR_NULL )
                    {
                        found = 1;
                        break;
                    }
                }

                if( found == 0 )
                {
                    // We do not have a PR_NULL
                    result = SetLastError(MAPI_E_INVALID_PARAMETER, 
                        "Invalid column in list.");
                    result = MAPI_E_INVALID_PARAMETER;
                    goto setcolumns_end;
                }
                else
                {
                    // We did have a PR_NULL
                    // Refresh defaultColumns in memory
                    defaultColumns[i] = lpPropTagArray->aulPropTag[j];

                    // Modify the SQL table
                    sqlObj->sqlCmdBegin();
                    sqlRes = sqlObj->sqlCommand(
                        "UPDATE %s SET PropTag = %d WHERE rowNum = -1 AND colID = %d",
                            tableName, lpPropTagArray->aulPropTag[j], i );
                    if( sqlRes != S_OK )
                    {
                        OTLKCON_DEBUGBREAK;
                    }
                    sqlObj->sqlCmdCommit();

                    newOrder.push_back( lpPropTagArray->aulPropTag[j] );
                }
            }
        }
        else
        {
            newOrder.push_back( lpPropTagArray->aulPropTag[j] );
        }
    }

    currentColumns.swap( newOrder );

    if( currentColumns.size() < 1 )
        OTLKCON_DEBUGBREAK;

 /*    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"\n=======\n\n=======\n\n=======\n\n");
    for( ULONG j=0; j<lpPropTagArray->cValues; j++ )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%S\n", 
            p2s->get( lpPropTagArray->aulPropTag[j] ) );
    }
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"\n=======\n\n");
    for( ULONG j=0; j<currentColumns.size(); j++ )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%S\n", 
            p2s->get( currentColumns[j] ) );
    }
    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"\n=======\n\n");
    for( ULONG j=0; j<defaultColumns.size(); j++ )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, L"%S\n", 
            p2s->get( defaultColumns[j] ) );
    }*/
setcolumns_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::SetColumns");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::SetColumns");

    return result;
}
 
STDMETHODIMP O_ITable::QueryColumns(ULONG ulFlags,LPSPropTagArray *lpPropTagArray)
{
    HRESULT result = S_OK;
    ULONG ulSize;

    Validate_IMAPITable_QueryColumns(this,ulFlags,lpPropTagArray);

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::QueryColumns");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::QueryColumns");

    lastCalledFunctionID = OTLKCON_ITABLE_QUERYCOLUMNS;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_QUERYCOLUMNS_STR);
    result = SetLastError(S_OK, "");

    if( ulFlags & TBL_ALL_COLUMNS )
    {
        //that is default column set
		result = GetColumnSet(lpPropTagArray);
//		// Get default column set
//        ulSize = (ULONG)defaultColumns.size();
//        *lpPropTagArray 
//            = (LPSPropTagArray)memObj->malloc( CbNewSPropTagArray(ulSize) );
//        (*lpPropTagArray)->cValues = ulSize;
//        for(ULONG i=0; i<ulSize; i++ )
//        {
//            (*lpPropTagArray)->aulPropTag[i] = defaultColumns[i];
//        }
    }
    else
    {
        // Get current column set
        ulSize = (ULONG)currentColumns.size();
        *lpPropTagArray 
            = (LPSPropTagArray)memObj->malloc( CbNewSPropTagArray(ulSize) );
        (*lpPropTagArray)->cValues = ulSize;
		ULONG ulRealColumns = 0;
        for(ULONG i=0; i<ulSize; i++ )
        {
            if(PR_NULL != currentColumns[i]) 
            {
				(*lpPropTagArray)->aulPropTag[ulRealColumns] = currentColumns[i];
				++ulRealColumns;
            } // if
        }
		(*lpPropTagArray)->cValues = ulRealColumns;
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::QueryColumns");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::QueryColumns");

    return result;
}

STDMETHODIMP O_ITable::GetRowCount(
    ULONG ulFlags,
    ULONG *lpulCount  
)
{
    HRESULT result = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::GetRowCount");

    lastCalledFunctionID = OTLKCON_ITABLE_GETROWCOUNT;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_GETROWCOUNT_STR);
    result = SetLastError(S_OK, "");

    *lpulCount = (ULONG)currentRows.size();

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_ITable::GetRowCount");

    return result;
}

STDMETHODIMP O_ITable::SeekRow(
  BOOKMARK bkOrigin,
  LONG lRowCount,
  LONG *lplRowsSought  
)
{
    HRESULT result = S_OK;
    long newCursor, bk;
    long found, pos;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::SeekRow");

    lastCalledFunctionID = OTLKCON_ITABLE_SEEKROW;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_SEEKROW_STR);
    result = SetLastError(S_OK, "");

    // calculate requested cursor 
    switch(bkOrigin)
    {
        case BOOKMARK_BEGINNING:
            // Assuming lRowCount is positive 
            // This needs to be checked 
            newCursor = (BOOKMARK)lRowCount;
            break;

        case BOOKMARK_CURRENT:
            // should check for neg results and overflow 
            if( cursor + lRowCount >= LONG_MAX )
            {
                result = SetLastError(MAPI_E_INVALID_PARAMETER, 
                                "Invalid arguments." );
                result = MAPI_E_INVALID_PARAMETER;
                goto seekrow_end;
            }
            newCursor = cursor + lRowCount;
            break;

        case BOOKMARK_END:
            // Assuming lRowCount is negative 
            // This needs to be checked 
            if( lRowCount >= 0 )
            {
                result = SetLastError(MAPI_E_INVALID_PARAMETER, 
                                "Invalid arguments." );
                result = MAPI_E_INVALID_PARAMETER;
                goto seekrow_end;
            }
            newCursor = (long)currentRows.size() + lRowCount;
            break;

        default:
            bk = bkOrigin - 10;
            bk = bookmarks[bk];

            // Is this row visible?
            found = 0; pos = 0;
            for( ULONG i=0; i<currentRows.size(); i++ )
                if( currentRows[i] == bk )
                {
                    found = 1;
                    pos = i;
                }

            if( found == 0 )
            {
                result = SetLastError(MAPI_E_INVALID_BOOKMARK, 
                                "Invalid bookmark." );
                result = MAPI_E_INVALID_BOOKMARK;
                goto seekrow_end;
            }
            newCursor = pos + lRowCount;
            break;
    }

    if( newCursor < 0 )
    {
        result = SetLastError(MAPI_E_INVALID_BOOKMARK, 
                                "Invalid bookmark." );
        result = MAPI_E_INVALID_BOOKMARK;
        goto seekrow_end;
    }

    if( lplRowsSought )
    {
        // TODO: Verify this is ok.
        *lplRowsSought = abs( newCursor - cursor );
    }
    
    cursor = newCursor;

seekrow_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::SeekRow");
    return result;
}

STDMETHODIMP O_ITable::SeekRowApprox(
  ULONG ulNumerator,
  ULONG ulDenominator 
)
{
    HRESULT result = S_OK;
    BOOKMARK newCursor;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::SeekRowApprox");

    lastCalledFunctionID = OTLKCON_ITABLE_SEEKROWAPPROX;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_SEEKROWAPPROX_STR);
    result = SetLastError(S_OK, "");

    if( ulDenominator == 0 )
    {
        result = SetLastError(MAPI_E_INVALID_PARAMETER, "Invalid parameter.");
        result = MAPI_E_INVALID_PARAMETER;
        goto seekrowapprox_end;
    }

    newCursor = ( ulNumerator / ulDenominator ) * currentRows.size();

    cursor = abs(newCursor);

seekrowapprox_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::SeekRowApprox");
    return result;
}

STDMETHODIMP O_ITable::QueryPosition(
  ULONG *lpulRow,
  ULONG *lpulNumerator,
  ULONG *lpulDenominator  
)
{
    HRESULT result = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::QueryPosition");

    lastCalledFunctionID = OTLKCON_ITABLE_QUERYPOSITION;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_QUERYPOSITION_STR);
    result = SetLastError(S_OK, "");

    *lpulRow = cursor;

    if( currentRows.size() < 1 )
    {
        *lpulNumerator = 0;
        *lpulDenominator = 1;
    }
    else
    {
        *lpulNumerator = cursor;
        *lpulDenominator = (ULONG)currentRows.size();
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::QueryPosition");
    return result;
}


// I'm assuming findrow() simply sets the iTable's cursor on success 
// since it doesn't have any out variables. 
STDMETHODIMP O_ITable::FindRow( LPSRestriction lpRestriction,
                          BOOKMARK BkOrigin,
                          ULONG ulFlags          
)
{
    HRESULT result  = S_OK;
    BOOL backward   = FALSE;
    ULONG start     = 0;
    BOOL found      = 0, vis = 0;
    LPSRow currRow = NULL;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::FindRow");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::FindRow");

    lastCalledFunctionID = OTLKCON_ITABLE_FINDROW;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_FINDROW_STR);
    SetLastError(S_OK, "");

    otlkcon_sqlite_helper * sqlObj = GetSqlObj();
    result = sqlObj->sqlCmdBegin();

    if( lpRestriction->rt == RES_SUBRESTRICTION )
    {
        // this one is handled differently 
        // make sure this is a folder 

        // TODO: come back to this after iFolder 
        OTLKCON_DEBUGBREAK;
        goto findrow_end;
    }

    if( currentRows.size() < 1 )
    {
        // Table view is empty
        result = MAPI_E_NOT_FOUND;
        goto findrow_end;
    }

    if( ulFlags & DIR_BACKWARD )
    {
        backward = TRUE;
    }

    switch(BkOrigin)
    {
        case BOOKMARK_BEGINNING:
            if( backward == TRUE )
            {
                // error: you can't start at the beginning and go backward 
                result = MAPI_E_NOT_FOUND;
                goto findrow_end;
            }
            start = 0;
            break;

        case BOOKMARK_CURRENT:
            start = cursor;
            break;

        case BOOKMARK_END:
            if( backward == FALSE )
            {
                // error: you can't start at the end and go forward 
                result = MAPI_E_NOT_FOUND;
                goto findrow_end;
            }
            start = (ULONG)currentRows.size() - 1;
            break;

        default:
            // FIXME: Handle bookmarks that where passed in
            OTLKCON_DEBUGBREAK;
            break;
    }

    found = FALSE;
	
	{
		TagSet tagSet;
		calculateTags(lpRestriction, tagSet);
		for(ULONG i=start; backward?i>=0:i<currentRows.size(); backward?i--:i++)
		{
			result = GetRowAt( currentRows[i], &currRow , &tagSet);
			result = doRestriction( currRow, lpRestriction, &vis );
			if( vis )
			{
				cursor = i;
				found = TRUE;
				break;
			}
			memObj->free(currRow);
			currRow = NULL;
		}
	}

    result = found?S_OK:MAPI_E_NOT_FOUND;

findrow_end:
    if( sqlObj->sqlCmdCommit() != S_OK )
    {
        OTLKCON_DEBUGBREAK;
    }
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::FindRow");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::FindRow");
    
    return result;
}

STDMETHODIMP O_ITable::Restrict(
  LPSRestriction lpRestriction,
  ULONG ulFlags                   
)
{
    HRESULT result = S_OK;
    LPSRow currRow = NULL;
    BOOL vis;
    vector<int> newRowSet;

	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetRowAt");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::Restrict");

    lastCalledFunctionID = OTLKCON_ITABLE_RESTRICT;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_RESTRICT_STR);
    result = SetLastError(S_OK, "");

    if( lpRestriction == NULL )
    {
        if (currentRestriction != NULL )
        {
            // Clear the current restriction
            vector<int> tmpRows( defaultRows );
            currentRows.swap( tmpRows );
        }

        // FIXME: Do we free currentRestriction?
        currentRestriction = NULL;
        
        result = S_OK;
        goto restrict_end;
    }

    // FIXME: Not quite.  Restriction structure may have pointers.
    //        This leads to calling freed memory.
    currentRestriction = lpRestriction;

    // Walk through all the available rows
	{
		TagSet tagSet;
		calculateTags(currentRestriction, tagSet);
		for(ULONG i=0,j=0; i<defaultRows.size(); i++)
		{
			result = GetRowAt(defaultRows[i], &currRow, &tagSet);
			result = doRestriction(currRow, lpRestriction, &vis);
			if( vis )
			{
				newRowSet.push_back( defaultRows[i] );
			}
			memObj->free(currRow);
			currRow = NULL;
		}
	}

    // Save the new view
    currentRows.swap( newRowSet );

    // FIXME: I'm not sure if that's correct, but setting
    //        the cursor to first row.  Confirm.
    cursor = 0;

restrict_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::Restrict");
	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::FindRow");
    return result;
}

STDMETHODIMP O_ITable::CreateBookmark(
  BOOKMARK *lpbkPosition  
)
{
    HRESULT result = S_OK;
    int rowNum;

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::CreateBookmark");

    lastCalledFunctionID = OTLKCON_ITABLE_CREATEBOOKMARK;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_CREATEBOOKMARK_STR);
    result = SetLastError(S_OK, OTLKCON_SUCCESS_ERROR_STR);

    if( currentRows.empty() )
    {
        result = SetLastError(MAPI_E_UNABLE_TO_COMPLETE, 
                                "The table view is empty" );
        result = MAPI_E_UNABLE_TO_COMPLETE;
        goto createbookmark_end;
    }

    rowNum = currentRows[cursor];

    bookmarks.push_back( rowNum );

    // Bookmark positions begin at 10
    // so to not to clash with predefined
    // bookmarks
    *lpbkPosition = (BOOKMARK)bookmarks.size() + 10;

    // RowNum seems to be a good choice for
    // the true bookmark value as they never
    // change.  But it is important that if
    // a row is deleted, then the 'bookmark'
    // vector be updated.

createbookmark_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::CreateBookmark");
    return result;
}

STDMETHODIMP O_ITable::FreeBookmark(
  BOOKMARK bkPosition  
)
{
    HRESULT result = S_OK;
    BOOKMARK bk;
    vector<BOOKMARK>::iterator iter = bookmarks.begin();

    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::FreeBookmark");

    lastCalledFunctionID = OTLKCON_ITABLE_FREEBOOKMARK;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_FREEBOOKMARK_STR);
    result = SetLastError(S_OK, OTLKCON_SUCCESS_ERROR_STR);

    if( bookmarks.empty() || bkPosition < 10 )
    {
        result = SetLastError(MAPI_E_INVALID_BOOKMARK, 
                                "Invalid bookmark." );
        result = MAPI_E_INVALID_BOOKMARK;
        goto freebookmark_end;
    }

    // FIXME: This is kinda awkward, but I can't seem to
    //        relate the array position with the vector
    //        iterator any other way.
    bk = bkPosition - 10;
    int found=0;
    for(ULONG i=0; i<bk && iter!=bookmarks.end(); i++, ++iter)
    {
        if( i==bk )
            found=1;
    }

    if( found )
    {
        bookmarks.erase( iter );
    }
    else
    {
        result = SetLastError(MAPI_E_INVALID_BOOKMARK, 
                                "Bookmark not found." );
        result = MAPI_E_INVALID_BOOKMARK;
        goto freebookmark_end;
    }

freebookmark_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::FreeBookmark");
    return result;
}

STDMETHODIMP O_ITable::SortTable(
  LPSSortOrderSet lpSortCriteria,
  ULONG ulFlags                     
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    lastCalledFunctionID = OTLKCON_ITABLE_SORTTABLE;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_SORTTABLE_STR);
    //SetLastError(MAPI_E_NO_SUPPORT, "Sorting is not supported");
    
    // Let's pretend...
    SetLastError(S_OK, "");
    result = S_OK;

    return result;
}

STDMETHODIMP O_ITable::QuerySortOrder(
  LPSSortOrderSet *lppSortCriteria  
)
{
  HRESULT result = MAPI_E_NO_SUPPORT;

    result = MAPI_E_NO_SUPPORT;

    return result;
}
 
STDMETHODIMP O_ITable::QueryRows(
  LONG lRowCount,
  ULONG ulFlags,
  LPSRowSet *lppRows  
)
{
    HRESULT result = S_OK;
    LPSRow tmpRow = NULL;
    LONG rowsRet =  0;
    int rowCountRequest = 0;

	OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::GetRowAt");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "O_ITable::QueryRows");

    lastCalledFunctionID = OTLKCON_ITABLE_QUERYROWS;
    strcpy(lastCalledFunctionName, OTLKCON_ITABLE_QUERYROWS_STR);
    SetLastError(S_OK, "");
     
    LONG viewSize = (LONG)currentRows.size(); 

    // Check that the request is reasonable
    if( lRowCount > OTLKCON_ITABLE_ROW_COUNT_MAX )
    {
        // We're never going to have more that that
        // many rows in the table anyway
        rowCountRequest = OTLKCON_ITABLE_ROW_COUNT_MAX;
    }
    else if( lRowCount < -OTLKCON_ITABLE_ROW_COUNT_MAX )
    {
        rowCountRequest = -OTLKCON_ITABLE_ROW_COUNT_MAX;
    }
    else
    {
        rowCountRequest = lRowCount;
    }

    if( cursor > viewSize )
    {
        // We forgot to reset cursor somewhere
        OTLKCON_DEBUGBREAK;
        cursor = viewSize;
    }

    // Adjust the cursor if needed before we start
    if( rowCountRequest < 0 )
    {
        // Reset cursor
        if( cursor + rowCountRequest < 0 )
        {
            // Return as many as we can
            rowsRet = cursor;
            cursor = 0;
        }
        else
        {
            rowsRet = -rowCountRequest;
            cursor -= rowsRet; 
        }
    }
    else if( rowCountRequest > 0 )
    {
        //FIXME: Overflow prone??
        if( (cursor + rowCountRequest) > viewSize )
        {
            // Return as many as we can
            rowsRet = viewSize - cursor;
        }
        else
        {
            rowsRet = rowCountRequest;
        }
    }
    else
    {
        // rowCountRequest == 0
        return MAPI_E_INVALID_PARAMETER;
    }

    *lppRows = (LPSRowSet)memObj->malloc( CbNewSRowSet(rowsRet) );
    (*lppRows)->cRows = rowsRet;
    if( rowsRet == 0 )
    {
        // No rows to return ??
        ;
    }

    for( LONG i=cursor,j=0,k=rowsRet+cursor; i<k; ++i, ++j )
    {
        result = GetRowAt( currentRows.at(i), &tmpRow );
        //TODO: Need to check returned value
		if( !(ulFlags & TBL_NOADVANCE) )
        {
            cursor++;
        }
		(*lppRows)->aRow[j].cValues = tmpRow->cValues;
		//according to the MAPI allocation rules for SRowSet structure
		//we need to allocate separate memory for props in each row
		//So that it can be freed via FreeProws call
		(*lppRows)->aRow[j].lpProps = memObj->propdup(tmpRow->cValues, tmpRow->lpProps);
		memObj->free(tmpRow);
		tmpRow = NULL;
		//memcpy(&((*lppRows)->aRow[j]),tmpRow,sizeof(SRow));
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "O_ITable::QueryRows"); 
	OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::FindRow");

    return S_OK;
}

STDMETHODIMP O_ITable::Abort()
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    result = MAPI_E_NO_SUPPORT;

    return result;
}

STDMETHODIMP O_ITable::ExpandRow(
  ULONG cbInstanceKey,
  LPBYTE pbInstanceKey,
  ULONG ulRowCount,
  ULONG ulFlags,
  LPSRowSet *lppRows,
  ULONG *lpulMoreRows  
)
{
  HRESULT result = MAPI_E_NO_SUPPORT;

  return result;
}

STDMETHODIMP O_ITable::CollapseRow(
  ULONG cbInstanceKey,
  LPBYTE pbInstanceKey,
  ULONG ulFlags,
  ULONG *lpulRowCount  
)
{
  HRESULT result = MAPI_E_NO_SUPPORT;

    result = MAPI_E_NO_SUPPORT;

  return result;
}

STDMETHODIMP O_ITable::WaitForCompletion(
  ULONG ulFlags,
  ULONG ulTimeout,
  ULONG *lpulTableStatus  
)
{
  HRESULT result = MAPI_E_NO_SUPPORT;
  
    result = MAPI_E_NO_SUPPORT;

    return result;
}

STDMETHODIMP O_ITable::GetCollapseState(
  ULONG ulFlags,
  ULONG cbInstanceKey,
  LPBYTE lpbInstanceKey,
  ULONG *lpcbCollapseState,
  LPBYTE *lppbCollapseState  
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    return result;
}

STDMETHODIMP O_ITable::SetCollapseState(
  ULONG ulFlags,
  ULONG cbCollapseState,
  LPBYTE pbCollapseState,
  BOOKMARK *lpbkLocation  
)
{
    HRESULT result = MAPI_E_NO_SUPPORT;

    return result;
}

STDMETHODIMP O_ITable::doAndRestriction( LPSRow row, 
                                        SAndRestriction res, BOOL *vis)
{
    HRESULT result = S_OK;
    BOOL anyFailed = FALSE;

    // I think it's a bitwise AND, so no short circuiting 
    for(ULONG i=0; i<res.cRes; i++)
    {
        // for every restriction 
        result = doRestriction(row, &(res.lpRes[i]), vis);
        if( *vis == FALSE )
            anyFailed = TRUE;
    }

    return S_OK;
}

STDMETHODIMP O_ITable::doBitmaskRestriction( LPSRow row, 
                                            SBitMaskRestriction res, BOOL *vis)
{
    HRESULT result = S_OK;
    ULONG value = 0;
    LPSPropValue propVal;

    FindPropInRow(row, res.ulPropTag, &propVal);

    if( propVal == NULL )
    {
        // This is actually an error condition 
        // The property was not found in the row 
        return MAPI_E_NOT_FOUND;
    }

    switch( PROP_TYPE(res.ulPropTag) )
    {
        // case PT_I2: 
    case PT_SHORT: // i 
        value = propVal->Value.i & res.ulMask;
        break;

        // case PT_I4: 
    case PT_LONG:  // l or ul - which one? this may be a problem 
        value = propVal->Value.l & res.ulMask;
        break;

        // case PT_R4: 
    case PT_FLOAT: // flt 
        // value = propVal->Value.flt & res.ulMask; 
        // value is left set to zero to indicate bitwise 'AND' failed 
        break;

        // case PT_R8: 
    case PT_DOUBLE: // dbl 
        // value = propVal->Value.dbl & res.ulMask; 
        break;

    case PT_BOOLEAN: // b 
        value = propVal->Value.b & res.ulMask;
        break;

    case PT_CURRENCY: // cur 
        // value = propVal->Value.cur & res.ulMask; 
        break;

    case PT_APPTIME: // at 
        // value = propVal->Value.at & res.ulMask; 
        break;

    case PT_SYSTIME: // ft 
        // value = propVal->Value.ft & res.ulMask; 
        break;

    case PT_STRING8: // lpszA 
        // value = propVal->Value.lpszA & res.ulMask; 
        break;

    case PT_BINARY: // bin
        value = 0;
        for( ULONG i=0; i<propVal->Value.bin.cb; i++)
        {
            if( propVal->Value.bin.lpb[i] & res.ulMask )
            {
                // Signifies TRUE
                value = 1;
            }
        }
        break;

    case PT_UNICODE: // lpszW 
        //value = propVal->Value.i & res.ulMask;
        break;

    case PT_CLSID: // lpguid 
        // value = propVal->Value.lpguid & res.ulMask; 
        break;

        // case PT_I8: 
    case PT_LONGLONG: // li 
        // value = propVal->Value.li & res.ulMask; 
        break;

    case PT_MV_I2: // MVi 
        value = 1;
        for(ULONG i=0;i<propVal->Value.MVi.cValues;i++)
        {
            if(! propVal->Value.MVi.lpi[i] & res.ulMask)
            {
                value = 0;
                break;
            }
        }
        break;

    case PT_MV_LONG: // MVl 
        value = 1;
        for(ULONG i=0;i<propVal->Value.MVl.cValues;i++)
        {
            if(! propVal->Value.MVl.lpl[i] & res.ulMask)
            {
                value = 0;
                break;
            }
        }
        break;

    case PT_MV_R4: // MVflt 
        // value = propVal->Value.MVflt & res.ulMask; 
        break;

    case PT_MV_DOUBLE: // MVdbl 
        // value = propVal->Value.MVdbl & res.ulMask; 
        break;

    case PT_MV_CURRENCY: // MVcur 
        // value = propVal->Value.MVcur & res.ulMask; 
        break;

    case PT_MV_APPTIME: // MVat 
        // value = propVal->Value.MVat & res.ulMask; 
        break;

    case PT_MV_SYSTIME: // MVft 
        // value = propVal->Value.MVft & res.ulMask; 
        break;

    case PT_MV_BINARY: // MVbin 
        // value = propVal->Value.MVbin & res.ulMask; 
        break;

    case PT_MV_STRING8: // MVszA 
        // value = propVal->Value.MVszA & res.ulMask; 
        break;

    case PT_MV_UNICODE: // MVszW 
        // value = propVal->Value.MVszW & res.ulMask; 
        break;

    case PT_MV_CLSID: // MVguid 
        // value = propVal->Value.MVguid & res.ulMask; 
        break;

    case PT_MV_I8: // MVli 
        //value = 1;
        //      for(ULONG i=0;i<propVal->Value.MVli.cValues;i++)
        //    {
        //        if(! (propVal->Value.MVli.lpli[i] & res.ulMask) )
        //       {
        //        value = 0;
        //         break;
        //       }
        //     }
        break;

    case PT_ERROR: // err 
        value = propVal->Value.err & res.ulMask;
        break;

    case PT_NULL:
    case PT_OBJECT: // x 
        value = propVal->Value.x & res.ulMask;;
        break;

    default:
        // error condition 
        value = 0;
        break;
    }

    switch ( res.relBMR )
    {
        case BMR_EQZ:
            if( value == 0 )
                *vis = TRUE;
            else *vis = FALSE;
            break;

        case BMR_NEZ:
            if( value != 0 )
                *vis = TRUE;
            else *vis = FALSE;
            break;

        default:
            // error condition 
            break;
    }

    return result;
}

STDMETHODIMP O_ITable::doCommentRestriction( LPSRow row, 
                                            SCommentRestriction res, BOOL *vis)
{
  // these aren't evaluated.  They stored though 
  // When are restrictions store? 
  HRESULT result = S_OK;

  return result;
}

STDMETHODIMP O_ITable::doComparePropsRestriction( LPSRow row, 
                                           SComparePropsRestriction res, BOOL *vis)
{
  HRESULT result = MAPI_E_NOT_FOUND;
  LPSPropValue propVal1, propVal2;

  if( PROP_TYPE(res.ulPropTag1) != PROP_TYPE(res.ulPropTag2) )
  {
    // same types only please 
    return MAPI_E_TOO_COMPLEX;
  }

  FindPropInRow(row, res.ulPropTag1, &propVal1);
  if( propVal1 == NULL )
  {
    // This is actually an error condition 
    // The property was not found in the row 
    return MAPI_E_NOT_FOUND;
  }

  FindPropInRow(row, res.ulPropTag2, &propVal2);
  if( propVal2 == NULL )
  {
    // This is actually an error condition 
    // The property was not found in the row 
    return MAPI_E_NOT_FOUND;
  }

  result = otlkcon_compare_props(res.relop, propVal1, propVal2, vis);

  return S_OK;
}

// this restriction seems to be only valid for string8 and binary properties 
// ie., according to the documention. What about unicode? 
STDMETHODIMP O_ITable::doContentRestriction( LPSRow row, 
                                         SContentRestriction restriction, BOOL *vis)
{
    HRESULT result = S_OK;
    unsigned short fuzzy_lower = 0;
    unsigned short fuzzy_upper = 0;
    char *tmpstr1, *tmpstr2;

    if( PROP_TYPE(restriction.ulPropTag) != PROP_TYPE(restriction.lpProp->ulPropTag) )
    {
        return MAPI_E_TOO_COMPLEX;
    }

    // get the lower 16 bits in ulFuzzyLevel.  
    fuzzy_lower |= ( restriction.ulFuzzyLevel & 0xFF );
    fuzzy_upper |= (( restriction.ulFuzzyLevel >> 16 ) & 0xFF );

    // this restriction needs to work through the entire row 
    for(ULONG i=0;i<row->cValues;i++)
    {  
        if( row->lpProps[i].ulPropTag == restriction.ulPropTag )
        {
            // there are several string type properties 
            switch( PROP_TYPE(restriction.ulPropTag) )
            {
                case PT_STRING8:
                    // allocate a new string for transformation and comparison
                    tmpstr1 
                        = (char *)memObj->_strdup( restriction.lpProp->Value.lpszA );
                    tmpstr2 
                        = (char *)memObj->_strdup( row->lpProps[i].Value.lpszA );

                    if(    (restriction.ulFuzzyLevel & FL_IGNORECASE) 
                        || (restriction.ulFuzzyLevel & FL_LOOSE) )
                    {
                        _strlwr(tmpstr1);
                        _strlwr(tmpstr2);
                    }

                    if( (fuzzy_upper & FL_IGNORENONSPACE) || (fuzzy_upper & FL_LOOSE)  )
                    {
                        // I'll ignore this one for now 
                    }

                    switch(fuzzy_lower) // or ( restriction.ulFuzzyLevel & 2^17 - 1 ) maybe 
                    {
                        case FL_FULLSTRING:
                            if(strcmp(tmpstr1, tmpstr2) == 0 )
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        case FL_PREFIX:
                            if(strncmp(tmpstr1, tmpstr2, strlen(tmpstr1)) == 0)
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        case FL_SUBSTRING:
                            if( strstr(tmpstr2, tmpstr1) )
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        default:
                            // error condition
                            OTLKCON_DEBUGBREAK;
                            break;
                    }
                    break;

                case PT_BINARY:
                    // compare string with binary.  why?? 
                    tmpstr1 
                        = (char *)memObj->_strdup( restriction.lpProp->Value.lpszA );
                    switch(fuzzy_lower)
                    {
                        case FL_FULLSTRING:
                            // size of a binary prop 
                            if( row->lpProps[i].Value.bin.cb == strlen(tmpstr1) &&
                                memcmp(tmpstr1, &(row->lpProps[i].Value.bin.lpb), 
                                strlen(tmpstr1)) == 0   )
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        case FL_PREFIX:
                            if( row->lpProps[i].Value.bin.cb >= strlen(tmpstr1) &&
                                memcmp(tmpstr1, row->lpProps[i].Value.bin.lpb, 
                                strlen(tmpstr1)) == 0)
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        case FL_SUBSTRING:
                            tmpstr2 
                                = (char *)memObj->malloc( row->lpProps[i].Value.bin.cb+1 ); 
                            memcpy(tmpstr2, row->lpProps[i].Value.bin.lpb, 
                                row->lpProps[i].Value.bin.cb);
                            if( strstr(tmpstr2, tmpstr1) )
                            {
                                *vis = TRUE;
                            }
                            else
                            {
                                *vis = FALSE;
                            }
                            break;

                        default:
                            // error condition 
                            break;
                    }
                    break;

                case PT_MV_STRING8:
                    // TODO 
                    break;

                case PT_MV_BINARY:
                    // TODO 
                    break;

                default:
                    // error condition 
                    // MAPI_E_TOO_COMPLEX ?? 
                    break;
            }

        }
    }

    // FIXME: buffers may not have been allocated.
    memObj->free(tmpstr1);
    memObj->free(tmpstr2);

    return result;
}

STDMETHODIMP O_ITable::doExistRestriction( LPSRow row, SExistRestriction restriction, BOOL *vis)
{
    HRESULT result = S_OK;

    if(FindPropInRow(row, restriction.ulPropTag, NULL)== S_OK)
    {
        *vis = TRUE;
    }
    else
    {
        *vis = FALSE;
    }

    return result;
}

STDMETHODIMP O_ITable::doNotRestriction( LPSRow row, 
                                        SNotRestriction restriction, BOOL *vis)
{
    HRESULT result = S_OK;
    SRestriction res;

    memcpy(&res, restriction.lpRes, sizeof(SRestriction));
    doRestriction(row, restriction.lpRes, vis);

    *vis = !(*vis);

    return S_OK;
}

STDMETHODIMP O_ITable::doOrRestriction( LPSRow row, SOrRestriction res, BOOL *vis)
{
    BOOL anyPassed = FALSE;
    HRESULT result = S_OK;

    // as with doAnd, not short circuit.
    // TODO: see if this is necessary 
    for(ULONG i=0; i<res.cRes; i++)
    {
        // for every restriction
        result = doRestriction(row, &(res.lpRes[i]), vis);
        if( *vis == TRUE )
            anyPassed = TRUE;
    }

    *vis = anyPassed;

    return S_OK;
}

STDMETHODIMP O_ITable::doPropertyRestriction( LPSRow row, 
                                             SPropertyRestriction res, BOOL *vis)
{
    HRESULT result = S_OK;
    LPSPropValue propVal;

    FindPropInRow(row, res.ulPropTag, &propVal);
    if( propVal == NULL )
    {
        // This is actually an error condition 
        // The property was not found in the row 
        return MAPI_E_NOT_FOUND;
    }

    if( PROP_TYPE(res.lpProp->ulPropTag) != PROP_TYPE(propVal->ulPropTag) )
    {
        // same types only please 
        return MAPI_E_TOO_COMPLEX;
    }

    otlkcon_compare_props(res.relop, res.lpProp, propVal, vis);

    return S_OK;
}

// I'm not sure this does the right thing as far as Value size is concerned 
STDMETHODIMP O_ITable::doSizeRestriction( LPSRow row, SSizeRestriction res, BOOL *vis)
{
    HRESULT result = S_OK;
    LPSPropValue propVal;
    size_t realSize = 0;

    FindPropInRow(row, res.ulPropTag, &propVal);
    if( propVal == NULL )
    {
        // This is actually an error condition 
        // The property was not found in the row 
        return MAPI_E_NOT_FOUND;
    }

    switch( PROP_TYPE(propVal->ulPropTag) )
    {
        // case PT_I2: 
        case PT_SHORT: // i 
            realSize = sizeof(short int);
            break;

        // case PT_I4: 
        case PT_LONG:  // l or ul 
            realSize =  sizeof(LONG); // or ULONG
            break;

        // case PT_R4: 
        case PT_FLOAT: // flt 
            realSize =  sizeof(float);
            break;

        // case PT_R8: 
        case PT_DOUBLE: // dbl 
            realSize =  sizeof(double);
            break;

        case PT_BOOLEAN: // b 
            realSize =  sizeof( unsigned short int );
            break;

        case PT_CURRENCY: // cur 
            realSize =  sizeof( CURRENCY );
            break;

        case PT_APPTIME: // at 
            realSize =  sizeof( double );
            break;

        case PT_SYSTIME: // ft 
            realSize =  sizeof( FILETIME );
            break;

        case PT_STRING8: // lpszA 
            realSize =  strlen( propVal->Value.lpszA );
            break;

        case PT_BINARY: // bin 
            realSize = propVal->Value.bin.cb;
            break;

        case PT_UNICODE: // lpszW 
            realSize =  wcslen( propVal->Value.lpszW );
            break;

        case PT_CLSID: // lpguid 
            realSize =  sizeof( LPGUID );
            break;

        // case PT_I8: 
        case PT_LONGLONG: // li 
            realSize =  sizeof( LARGE_INTEGER );
            break;

        // for multi-values, I'll expand the pointers 
        // TODO: Confirm that this is the correct behavior 
        case PT_MV_I2: // MVi 
            realSize =  sizeof(SShortArray) + propVal->Value.MVi.cValues * sizeof(short);
            break;

        case PT_MV_LONG: // MVl 
            realSize =  sizeof(SLongArray) + propVal->Value.MVl.cValues * sizeof(LONG);
            break;

        case PT_MV_R4: // MVflt 
            realSize =  sizeof(SRealArray) + propVal->Value.MVflt.cValues * sizeof(float);
            break;

        case PT_MV_DOUBLE: // MVdbl 
            realSize =  sizeof(SDoubleArray) + propVal->Value.MVdbl.cValues * sizeof(double);
            break;

        case PT_MV_CURRENCY: // MVcur 
            realSize =  sizeof(SCurrencyArray) + propVal->Value.MVcur.cValues * sizeof(CURRENCY);
            break;

        case PT_MV_APPTIME: // MVat 
            realSize =  sizeof(SAppTimeArray) + propVal->Value.MVat.cValues * sizeof(double);
            break;

        case PT_MV_SYSTIME: // MVft 
            realSize =  sizeof(SDateTimeArray) + propVal->Value.MVft.cValues * sizeof(FILETIME);
            break;

        case PT_MV_BINARY: // MVbin 
            for(ULONG i=0;i<propVal->Value.MVbin.cValues;i++)
                realSize += propVal->Value.MVbin.lpbin[i].cb + sizeof(SBinary);
            realSize +=  sizeof(SBinaryArray);
            break;

        case PT_MV_STRING8: // MVszA 
            for(ULONG i=0;i<propVal->Value.MVszA.cValues;i++)
            {
            realSize += strlen(propVal->Value.MVszA.lppszA[i]);
            }
            realSize +=  sizeof( SLPSTRArray );
            break;

        case PT_MV_UNICODE: // MVszW 
            for(ULONG i=0;i<propVal->Value.MVszW.cValues;i++)
            {
            realSize += wcslen(propVal->Value.MVszW.lppszW[i]);
            }
            realSize +=  sizeof( SWStringArray );
            break;

        case PT_MV_CLSID: // MVguid 
            realSize =  sizeof( SGuidArray )+ propVal->Value.MVguid.cValues * sizeof(GUID);
            break;

        case PT_MV_I8: // MVli 
            realSize =  sizeof( SLargeIntegerArray )+ propVal->Value.MVli.cValues * sizeof(LARGE_INTEGER);
            break;

        case PT_ERROR: // err 
            realSize =  sizeof( SCODE );
            break;

        case PT_NULL:
        case PT_OBJECT: // x 
            realSize =  sizeof( LONG );
            break;

        default:
            break;
    }

    // assuming "first value" is the tag 
    // FIXME:  This needs to be verified
    switch(res.relop)
    {
        case RELOP_GE:
            if( realSize >= res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_GT:
            if( realSize > res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_LE:
            if( realSize <= res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_LT:
            if( realSize < res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_NE:
            if( realSize != res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_RE:
            if( realSize == res.cb )
            {
                *vis = TRUE;
            }
            break;

        case RELOP_EQ:
            if( realSize == res.cb )
            {
                *vis = TRUE;
            }
            break;
    }

    return result;
}

//  not clear on this 
// run through a folder's contents applying a restriction to every message's 
// recepient and attachment tables? 
STDMETHODIMP O_ITable::doSubRestriction( LPSRow row, SSubRestriction res, BOOL *vis)
{
  HRESULT result = MAPI_E_NOT_FOUND;

  // TODO: Make sure this is a folder and not a message iTable 

  // I'll return to this after iFolder 

  return result;
}

STDMETHODIMP O_ITable::calculateTags(LPSRestriction restriction, TagSet &tagSet)
{
	HRESULT hResult = S_OK;
	
	switch(restriction->rt)
	{
	case RES_AND:
		for(ULONG iRes = 0; iRes < restriction->res.resAnd.cRes; iRes++)
		{
			calculateTags(&restriction->res.resAnd.lpRes[iRes], tagSet);
		} // for
		break;
	case RES_BITMASK:
		tagSet.insert(restriction->res.resBitMask.ulPropTag);
		break;

	case RES_COMMENT:
		//just ignore
		break;

	case RES_COMPAREPROPS:
		tagSet.insert(restriction->res.resCompareProps.ulPropTag1);
		tagSet.insert(restriction->res.resCompareProps.ulPropTag2);
		break;

	case RES_CONTENT:
		tagSet.insert(restriction->res.resContent.ulPropTag);
		break;

	case RES_EXIST:
		tagSet.insert(restriction->res.resExist.ulPropTag);
		break;

	case RES_NOT:
		calculateTags(restriction->res.resNot.lpRes, tagSet);
		break;

	case RES_OR:
		for(ULONG iRes = 0; iRes < restriction->res.resOr.cRes; iRes++)
		{
			calculateTags(&restriction->res.resOr.lpRes[iRes], tagSet);
		} // for
		break;

	case RES_PROPERTY:
		tagSet.insert(restriction->res.resProperty.ulPropTag);
		break;

	case RES_SIZE:
		tagSet.insert(restriction->res.resSize.ulPropTag);
		break;

	case RES_SUBRESTRICTION:
		//just ignore
		break;
	default:
		// error condition 
		OTLKCON_DEBUGBREAK;
		break;
	}

	return hResult;
}

STDMETHODIMP O_ITable::doRestriction( LPSRow row, 
                                            LPSRestriction restriction, BOOL *vis)
{
    HRESULT result = S_OK;

    if( restriction == NULL )
    {
        *vis = FALSE;
        return MAPI_E_INVALID_PARAMETER;
    }

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::doRestriction");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> ITable::doRestriction");

    switch(restriction->rt)
    {
        case RES_AND:
            result = doAndRestriction(row, restriction->res.resAnd, vis);
            break;

        case RES_BITMASK:
            result = doBitmaskRestriction(row, restriction->res.resBitMask, vis);
            break;

        case RES_COMMENT:
            result = doCommentRestriction(row, restriction->res.resComment, vis);
            break;

        case RES_COMPAREPROPS:
            result = doComparePropsRestriction(row, 
                                            restriction->res.resCompareProps, vis);
            break;

        case RES_CONTENT:
            result = doContentRestriction(row, restriction->res.resContent, vis);
            break;

        case RES_EXIST:
            result = doExistRestriction(row, restriction->res.resExist, vis);
            break;

        case RES_NOT:
            result = doNotRestriction(row, restriction->res.resNot, vis);
            break;

        case RES_OR:
            result = doOrRestriction(row, restriction->res.resOr, vis);
            break;

        case RES_PROPERTY:
            result = doPropertyRestriction(row, restriction->res.resProperty, vis);
            break;

        case RES_SIZE:
            result = doSizeRestriction(row, restriction->res.resSize, vis);
            break;

        case RES_SUBRESTRICTION:
            result = doSubRestriction(row, restriction->res.resSub, vis);
            break;

        default:
            // error condition 
            OTLKCON_DEBUGBREAK;
            break;
    }

    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> ITable::doRestriction");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> ITable::doRestriction");

    return result;
}

// Return the address of a property in a table row 
// PS. May be useful in the future to have a companion 
// function that returns the index instead of the address 
HRESULT O_ITable::FindPropInRow(LPSRow row, ULONG propTag, LPSPropValue *propVal)
{

    for(ULONG i=0; i<row->cValues; i++)
    {
        if(row->lpProps[i].ulPropTag == propTag)
        {
            if(propVal)
                *propVal = &(row->lpProps[i]);
            return S_OK;
        }
    }

    if(propVal)
        *propVal = NULL;
    return E_FAIL;
}

STDMETHODIMP O_ITable::GetColumnSet(LPSPropTagArray *lppTags)
{
	*lppTags = NULL; 
	memObj->lpAllocateBuffer(CbNewSPropTagArray((ULONG)defaultColumns.size()), (LPVOID*)lppTags);
	
	if(NULL != *lppTags)
	{
		//default size
		(*lppTags)->cValues = (ULONG)defaultColumns.size();

		ULONG ulRealCount = 0;
		for(ULONG iTag = 0; iTag < (*lppTags)->cValues; iTag++)
		{
			if(PR_NULL != defaultColumns[iTag])
			{
				(*lppTags)->aulPropTag[ulRealCount] = defaultColumns[iTag];
				++ulRealCount;
			} // if
		} // for
		(*lppTags)->cValues = ulRealCount;
	} // if

	return (NULL != *lppTags)?S_OK:E_OUTOFMEMORY;
}

STDMETHODIMP O_ITable::HrUpdateObjectRow(ULONG ulFlags, O_IProp *lpObject)
{
	//if no columns no need to update table
	if(0 == defaultColumns.size())
	{
		return S_OK;
	} // if
	
	LPSPropTagArray lpPropTags = NULL;
	HRESULT hResult = GetColumnSet(&lpPropTags);
	
	if(SUCCEEDED(hResult))
	{
		ULONG cValues = 0;
		LPSPropValue lpProps = NULL;

		hResult = lpObject->GetProps(lpPropTags, 0, &cValues, &lpProps);
		if(SUCCEEDED(hResult))
		{
			//+1 is for PR_INSTANCE_KEY if needed
			LPSPropValue lpRealProps = (LPSPropValue)memObj->malloc(sizeof(SPropValue)*(cValues+1));
			if(NULL != lpRealProps)
			{
				ULONG ulPropCount = 0;
				BOOL bNeedInstance = TRUE;

				for(ULONG iProp = 0; iProp < cValues; iProp++)
				{
					if(PT_ERROR != PROP_TYPE(lpProps[iProp].ulPropTag))
					{
						lpRealProps[ulPropCount] = lpProps[iProp];
						if(PR_INSTANCE_KEY == lpProps[iProp].ulPropTag)
						{
							bNeedInstance = FALSE;
						} // if
						++ulPropCount;
					} // if
				} // for
				
				if(bNeedInstance)
				{
					lpRealProps[ulPropCount].ulPropTag = PR_INSTANCE_KEY;
					lpRealProps[ulPropCount].Value.bin.cb = sizeof(MAPIUID);
					lpRealProps[ulPropCount++].Value.bin.lpb 
                        = (LPBYTE)&(lpObject->entryID->instanceGuid);
				} // if
				
				SRow rowObject = {0};
				rowObject.cValues = ulPropCount;
				rowObject.lpProps = lpRealProps;

				if(UPDATE_SNAPSHOT_ONLY & ulFlags) 
				{
					if(NULL != lpTblData)
					{
						hResult = lpTblData->HrModifyRow(&rowObject);
					} // if
				} // if
				else
				{
					hResult = HrModifyRow(&rowObject);
				} // else
				
				memObj->free(lpRealProps);
			} // if

			memObj->lpFreeBuffer(lpProps);
		} // if
		memObj->lpFreeBuffer(lpPropTags);
	} // if
	return hResult;
}

STDMETHODIMP O_ITable::GetSnapShot(LPTABLEDATA* lppTable)
{
	HRESULT hResult = E_INVALIDARG;
	if(NULL != lppTable)
	{
		*lppTable = lpTblData;
		lpTblData->AddRef();
		hResult = S_OK;
	} // if

	return hResult;
}

O_ITableAdviseSink::O_ITableAdviseSink( O_ITable *tbl )
{
    cRef = 0;
    table = tbl;
}

O_ITableAdviseSink::~O_ITableAdviseSink()
{
    ;
}

STDMETHODIMP O_ITableAdviseSink::QueryInterface (REFIID riid, void **ppv)
{
    HRESULT result = S_OK;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, L"inside O_ITableAdviseSink::QueryInterface. \
 {%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}\n",
        riid.Data1, riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1],
        riid.Data4[2], riid.Data4[3], riid.Data4[4], riid.Data4[5],
        riid.Data4[6], riid.Data4[7]);

    *ppv = NULL;

    if( memcmp(&riid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(&riid, &IID_IMAPIAdviseSink, sizeof(IID)))
    {
        result = ResultFromScode(E_NOINTERFACE);
        return result;
    }

    AddRef();
    *ppv = (LPVOID)this;

    return result;
}

ULONG STDMETHODCALLTYPE O_ITableAdviseSink::AddRef (void)
{
    cRef++;

    return cRef;
}

ULONG STDMETHODCALLTYPE O_ITableAdviseSink::Release (void)
{
    return --cRef;
}

ULONG O_ITableAdviseSink::OnNotify( ULONG cNotif, LPNOTIFICATION lpNotifications)
{
    ULONG result = S_OK;
    LPNOTIFICATION currNotif;
    LPSPropValue tmpProp = NULL;

    for( ULONG i=0; i<cNotif; i++)
    {
        currNotif = lpNotifications+i;

        //Is this an event we care about?
        switch( currNotif->ulEventType)
        {
            case fnevTableModified:
                {
                    switch(currNotif->info.tab.ulTableEvent)
                    {
                        case TABLE_ROW_DELETED:
                            tmpProp = &(currNotif->info.tab.propIndex);
                            result = table->lpTblData->HrDeleteRow( tmpProp );
                            if( FAILED(result) )
                            {
                                OTLKCON_DEBUGBREAK;
                            }
                            break;

                        default:
                            OTLKCON_DEBUGBREAK;
                    }
                }
                break;

            default:
                OTLKCON_DEBUGBREAK
                break;
        }
    }

    return S_OK;
}

